apiutils

package module
v0.0.0-...-6391ce2 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2020 License: Apache-2.0 Imports: 5 Imported by: 0

README

APIUtils

Pipeline codecov Go Report Card GoDoc

A handy collection of utility libs for designing apis in go

Codecs

Codecs should live seperate to handlers. The lines between business logic and data encoding should be kept sharp. The codec package enables this to be done.

e.g. a simple helloworld application

package main

import (
	"net/http"
	"time"

	"github.com/JoeReid/apiutils"
	"github.com/JoeReid/apiutils/jsoncodec"
	"github.com/JoeReid/apiutils/yamlcodec"
)

type HelloEndpoint struct {
	// DB etc...
}

func (h *HelloEndpoint) ServeCodec(c apiutils.Codec, w http.ResponseWriter, r *http.Request) {
	type Hello struct {
		Hello string    `json:"hello" yaml:"yamlhello"`
		Time  time.Time `json:"timestamp" yaml:"yamltimestamp"`
	}

	// Respond back with a simple hello world message
	c.Respond(r.Context(), w, http.StatusOK, &Hello{
		Hello: "world",
		Time:  time.Now(),
	})
}

func main() {
	hello := &HelloEndpoint{}

	http.Handle("/json", apiutils.HandlerWithCodec(jsoncodec.New(), hello))
	http.Handle("/yaml", apiutils.HandlerWithCodec(yamlcodec.New(), hello))
	http.ListenAndServe(":8080", nil)
}
$ curl 'localhost:8080/json'
{"hello":"world","timestamp":"2020-06-27T01:29:38.271839357+01:00"}

$ curl 'localhost:8080/yaml'
yamlhello: world
yamltimestamp: 2020-06-27T01:29:42.85396202+01:00
But wait, theres more

Why not let the api consumer decide the format they want

package main

import (
	"net/http"
	"time"

	"github.com/JoeReid/apiutils"
	"github.com/JoeReid/apiutils/jsoncodec"
	"github.com/JoeReid/apiutils/yamlcodec"
)

type HelloEndpoint struct {
	// DB etc...
}

func (h *HelloEndpoint) ServeCodec(c apiutils.Codec, w http.ResponseWriter, r *http.Request) {
	type Hello struct {
		Hello string    `json:"hello" yaml:"yamlhello"`
		Time  time.Time `json:"timestamp" yaml:"yamltimestamp"`
	}

	// Respond back with a simple hello world message
	c.Respond(r.Context(), w, http.StatusOK, &Hello{
		Hello: "world",
		Time:  time.Now(),
	})
}

func main() {
	// configure all the codec options
	codecSelector, _ := apiutils.NewRequestSelector(
		apiutils.RegisterCodec(
			jsoncodec.New(), "json", "application/json"),

		apiutils.RegisterCodec(
			jsoncodec.New(jsoncodec.SetIndent("", "\t")),
			"json,pretty", "application/json,pretty"),

		apiutils.RegisterCodec(
			yamlcodec.New(), "yaml", "application/x-yaml"),
	)

	// serve all the codecs on a common endpoint
	hello := &HelloEndpoint{}
	http.Handle("/hello", apiutils.HandlerWithSelector(codecSelector, hello))
	http.ListenAndServe(":8080", nil)
}
$ curl 'localhost:8080/hello?codec=json'
{"hello":"world","timestamp":"2020-06-27T01:32:01.945250157+01:00"}

$ curl 'localhost:8080/hello?codec=yaml'
yamlhello: world
yamltimestamp: 2020-06-27T01:32:06.962818404+01:00

Pagination

Write your handler to fetch paginated data, and let the lib wory about getting the values from the consumer.

package main

import (
	"net/http"

	"github.com/JoeReid/apiutils"
	"github.com/JoeReid/apiutils/jsoncodec"
	"github.com/JoeReid/apiutils/yamlcodec"
)

type PaginationExample struct {
	// DB etc...
}

func (p *PaginationExample) ServeCodec(c apiutils.Codec, w http.ResponseWriter, r *http.Request) {
	count, skip, err := apiutils.Paginate(r, apiutils.DefaultCount(10), apiutils.MaxCount(10))
	if err != nil {
		c.Respond(r.Context(), w, http.StatusBadRequest, err)
		return
	}

	// build a slice of ints to simulate paginated data
	data := make([]int, count)
	for i := 0; i < count; i++ {
		data[i] = (count * skip) + i
	}

	// Respond with this fragment of the data
	c.Respond(r.Context(), w, http.StatusOK, data)
}

func main() {
	// configure all the codec options
	codecSelector, _ := apiutils.NewRequestSelector(
		apiutils.RegisterCodec(jsoncodec.New(), "json", "application/json"),
		apiutils.RegisterCodec(yamlcodec.New(), "yaml", "application/x-yaml"),
	)

	paginate := &PaginationExample{}
	http.Handle("/paginate", apiutils.HandlerWithSelector(codecSelector, paginate))
	http.ListenAndServe(":8080", nil)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func HandlerWithCodec

func HandlerWithCodec(c Codec, h Handler) http.Handler

func HandlerWithSelector

func HandlerWithSelector(s CodecSelector, h Handler) http.Handler

func NewRequestSelector

func NewRequestSelector(opts ...RequestSelectorOption) (*requestSelector, error)

func Paginate

func Paginate(r *http.Request, opts ...PaginateOption) (count int, skip int, err error)

func SetCodecURLKey

func SetCodecURLKey(k string) func(r *requestSelector)

Types

type Codec

type Codec interface {
	// Encode and write the given data to the response writer with the requested status code
	// Will change the status code apropriately if there is an encoding error
	Respond(ctx context.Context, w http.ResponseWriter, code int, data interface{})

	// Read will attempt to decode the body of a request onto the given data interface
	Read(ctx context.Context, r *http.Request, data interface{}) error
}

type CodecSelector

type CodecSelector interface {
	For(r *http.Request) Codec
}

type Handler

type Handler interface {
	ServeCodec(c Codec, w http.ResponseWriter, r *http.Request)
}

type HandlerFunc

type HandlerFunc func(c Codec, w http.ResponseWriter, r *http.Request)

func (*HandlerFunc) ServeCodec

func (h *HandlerFunc) ServeCodec(c Codec, w http.ResponseWriter, r *http.Request)

type PaginateOption

type PaginateOption func(*pagOpts) error

func DefaultCount

func DefaultCount(n int) PaginateOption

func DefaultSkip

func DefaultSkip(n int) PaginateOption

func MaxCount

func MaxCount(n int) PaginateOption

func MaxSkip

func MaxSkip(n int) PaginateOption

type RequestSelectorOption

type RequestSelectorOption func(r *requestSelector)

func RegisterCodec

func RegisterCodec(c Codec, values ...string) RequestSelectorOption

func SetDefaultCodec

func SetDefaultCodec(c Codec) RequestSelectorOption

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

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