fn

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: May 19, 2022 License: Apache-2.0 Imports: 9 Imported by: 17

README

fn

This library aims to simplify the construction of JSON API service, fn.Wrap is able to wrap any function to adapt the interface of http.Handler, which unmarshals POST data to a struct automatically.

Benchmark

BenchmarkIsBuiltinType-8                50000000                33.5 ns/op             0 B/op          0 allocs/op
BenchmarkSimplePlainAdapter_Invoke-8     2000000               757 ns/op             195 B/op          3 allocs/op
BenchmarkSimpleUnaryAdapter_Invoke-8     2000000               681 ns/op             946 B/op          5 allocs/op
BenchmarkGenericAdapter_Invoke-8         2000000               708 ns/op             946 B/op          5 allocs/op

Support types

io.ReadCloser      // request.Body
http.Header        // request.Header
fn.Form        // request.Form
fn.PostForm    // request.PostForm
*fn.Form       // request.Form
*fn.PostForm   // request.PostForm
*url.URL           // request.URL
*multipart.Form    // request.MultipartForm
*http.Request      // raw request

Usage

http.Handle("/test", fn.Wrap(test))

func test(io.ReadCloser, http.Header, fn.Form, fn.PostForm, *CustomizedRequestType, *url.URL, *multipart.Form) (*CustomizedResponseType, error)

Examples

Basic
package examples

import (
	"io"
	"mime/multipart"
	"net/http"
	"net/url"

	"github.com/pingcap/fn"
)

type Request struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type Response struct {
	Token string `json:"token"`
}

func api1() (*Response, error) {
	return &Response{Token: "token"}, nil
}

func api2(request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api3(rawreq *http.Request, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api4(rawreq http.Header, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api5(form *fn.Form, request *Request) (*Response, error) {
	token := request.Username + request.Password + form.Get("type")
	return &Response{Token: token}, nil
}

func api6(body io.ReadCloser, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api7(form *multipart.Form, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api7(urls *url.URL, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}

func api8(urls *url.URL, form *multipart.Form, body io.ReadCloser, rawreq http.Header, request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}
Plugins
package examples

import (
	"context"
	"errors"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"strings"

	"github.com/pingcap/fn"
)

var PermissionDenied = errors.New("permission denied")

func logger(ctx context.Context, req *http.Request) (context.Context, error) {
	log.Println("Request", req.RemoteAddr, req.URL.String())
	return ctx, nil
}

func ipWhitelist(ctx context.Context, req *http.Request) (context.Context, error) {
	if strings.HasPrefix(req.RemoteAddr, "172.168") {
		return ctx, PermissionDenied
	}
	return ctx, nil
}

func auth(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	return ctx, nil
}

type Request struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type Response struct {
	Token string `json:"token"`
}

func example() {
	fn.Plugin(logger, ipWhitelist, auth)
	http.Handle("/api1", fn.Wrap(api1))
	http.Handle("/api2", fn.Wrap(api2))
}

// api1 and api2 request have be validated by `ipWhitelist` and `auth`

func api1() (*Response, error) {
	return &Response{Token: "token"}, nil
}

func api2(request *Request) (*Response, error) {
	token := request.Username + request.Password
	return &Response{Token: token}, nil
}
fn.Group
package examples

import (
	"context"
	"errors"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"strings"

	"github.com/pingcap/fn"
)

var PermissionDenied = errors.New("permission denied")

func logger(ctx context.Context, req *http.Request) (context.Context, error) {
	log.Println("Request", req.RemoteAddr, req.URL.String())
	return ctx, nil
}

func ipWhitelist(ctx context.Context, req *http.Request) (context.Context, error) {
	if strings.HasPrefix(req.RemoteAddr, "172.168") {
		return ctx, PermissionDenied
	}
	return ctx, nil
}

func auth(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	return ctx, nil
}

type User struct {
	Balance int64
}

func queryUserFromRedis(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	user := &User{
		Balance: 10000, // balance from redis
	}
	return context.WithValue(ctx, "user", user), nil
}

type Response struct {
	Balance int64 `json:"balance"`
}

func example() {
	// Global plugins
	fn.Plugin(logger, ipWhitelist, auth)

	group := fn.NewGroup()

	// Group plugins
	group.Plugin(queryUserFromRedis)
	http.Handle("/user/balance", group.Wrap(fetchBalance))
	http.Handle("/user/buy", group.Wrap(buy))
}

func fetchBalance(ctx context.Context) (*Response, error) {
	user := ctx.Value("user").(*User)
	return &Response{Balance: user.Balance}, nil
}

func buy(ctx context.Context) (*Response, error) {
	user := ctx.Value("user").(*User)
	if user.Balance < 100 {
		return nil, errors.New("please check balance")
	}
	user.Balance -= 100
	return &Response{Balance: user.Balance}, nil
}
ResponseEncoder
package examples

import (
	"context"
	"errors"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"strings"

	"github.com/pingcap/fn"
)

var PermissionDenied = errors.New("permission denied")

func logger(ctx context.Context, req *http.Request) (context.Context, error) {
	log.Println("Request", req.RemoteAddr, req.URL.String())
	return ctx, nil
}

func ipWhitelist(ctx context.Context, req *http.Request) (context.Context, error) {
	if strings.HasPrefix(req.RemoteAddr, "172.168") {
		return ctx, PermissionDenied
	}
	return ctx, nil
}

func auth(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	return ctx, nil
}

func injectRequest(ctx context.Context, req *http.Request) (context.Context, error) {
	return context.WithValue(ctx, "_rawreq", req), nil
}

type User struct {
	Balance int64
}

func queryUserFromRedis(ctx context.Context, req *http.Request) (context.Context, error) {
	token := req.Header.Get("X-Auth-token")
	_ = token // Validate token (e.g: query db)
	if token != "valid" {
		return ctx, fn.ErrorWithStatusCode(PermissionDenied, http.StatusForbidden)
	}
	user := &User{
		Balance: 10000, // balance from redis
	}
	return context.WithValue(ctx, "user", user), nil
}

type Response struct {
	Balance int64 `json:"balance"`
}

type ResponseMessage struct {
	Code int         `json:"code"`
	Data interface{} `json:"data"`
}

type ErrorMessage struct {
	Code  int    `json:"code"`
	Error string `json:"error"`
}

func example() {
	// Global plugins
	fn.Plugin(logger, ipWhitelist, auth, injectRequest)
	// Uniform all responses
	fn.SetErrorEncoder(func(ctx context.Context, err error) interface{} {
		req := ctx.Value("_rawreq").(*http.Request)
		log.Println("Error occurred: ", req.URL, err)
		return &ErrorMessage{
			Code:  -1,
			Error: err.Error(),
		}
	})

	fn.SetResponseEncoder(func(ctx context.Context, payload interface{}) interface{} {
		return &ResponseMessage{
			Code: 1,
			Data: payload,
		}
	})

	group := fn.NewGroup()

	// Group plugins
	group.Plugin(queryUserFromRedis)
	http.Handle("/user/balance", group.Wrap(fetchBalance))
	http.Handle("/user/buy", group.Wrap(buy))
}

func fetchBalance(ctx context.Context) (*Response, error) {
	user := ctx.Value("user").(*User)
	return &Response{Balance: user.Balance}, nil
}

func buy(ctx context.Context) (*Response, error) {
	user := ctx.Value("user").(*User)
	if user.Balance < 100 {
		return nil, errors.New("please check balance")
	}
	user.Balance -= 100
	return &Response{Balance: user.Balance}, nil
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ErrorWithStatusCode

func ErrorWithStatusCode(err error, statusCode int) error

func Plugin

func Plugin(plugins ...PluginFunc)

func SetErrorEncoder

func SetErrorEncoder(c ErrorEncoder)

func SetMultipartFormMaxMemory

func SetMultipartFormMaxMemory(m int64)

func SetResponseEncoder

func SetResponseEncoder(c ResponseEncoder)

func Unwrap

func Unwrap(err error) error

func UnwrapErrorStatusCode

func UnwrapErrorStatusCode(err error) (int, bool)

func Wrap

func Wrap(f interface{}) *fn

Types

type ErrorEncoder

type ErrorEncoder func(ctx context.Context, err error) interface{}

ErrorEncoder encode error to response body

type Form

type Form struct {
	// contains filtered or unexported fields
}

func (*Form) Add

func (f *Form) Add(key, value string)

Add adds the value to key. It appends to any existing values associated with key.

func (*Form) Del

func (f *Form) Del(key string)

Del deletes the values associated with key.

func (*Form) Encode

func (f *Form) Encode() string

Encode encodes the values into “URL encoded” form ("bar=baz&foo=quux") sorted by key.

func (*Form) Get

func (f *Form) Get(key string) string

Get gets the first value associated with the given key. If there are no values associated with the key, Get returns the empty string. To access multiple values, use the map directly.

func (*Form) Int

func (f *Form) Int(key string) int

func (*Form) Int64

func (f *Form) Int64(key string) int64

func (*Form) Int64OrDefault

func (f *Form) Int64OrDefault(key string, def int64) int64

func (*Form) IntOrDefault

func (f *Form) IntOrDefault(key string, def int) int

func (*Form) Set

func (f *Form) Set(key, value string)

Set sets the key to value. It replaces any existing values.

func (*Form) Uint64

func (f *Form) Uint64(key string) uint64

func (*Form) Uint64OrDefault

func (f *Form) Uint64OrDefault(key string, def uint64) uint64

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group represents a handler group that contains same hooks

func NewGroup

func NewGroup() *Group

func (*Group) Plugin

func (g *Group) Plugin(plugins ...PluginFunc) *Group

func (*Group) Wrap

func (g *Group) Wrap(f interface{}) *fn

type PluginFunc

type PluginFunc func(context.Context, *http.Request) (context.Context, error)

type PostForm

type PostForm struct {
	// contains filtered or unexported fields
}

func (*PostForm) Add

func (f *PostForm) Add(key, value string)

Add adds the value to key. It appends to any existing values associated with key.

func (*PostForm) Del

func (f *PostForm) Del(key string)

Del deletes the values associated with key.

func (*PostForm) Encode

func (f *PostForm) Encode() string

Encode encodes the values into “URL encoded” form ("bar=baz&foo=quux") sorted by key.

func (*PostForm) Get

func (f *PostForm) Get(key string) string

Get gets the first value associated with the given key. If there are no values associated with the key, Get returns the empty string. To access multiple values, use the map directly.

func (*PostForm) Int

func (f *PostForm) Int(key string) int

func (*PostForm) Int64

func (f *PostForm) Int64(key string) int64

func (*PostForm) Int64OrDefault

func (f *PostForm) Int64OrDefault(key string, def int64) int64

func (*PostForm) IntOrDefault

func (f *PostForm) IntOrDefault(key string, def int) int

func (*PostForm) Set

func (f *PostForm) Set(key, value string)

Set sets the key to value. It replaces any existing values.

func (*PostForm) Uint64

func (f *PostForm) Uint64(key string) uint64

func (*PostForm) Uint64OrDefault

func (f *PostForm) Uint64OrDefault(key string, def uint64) uint64

type ResponseEncoder

type ResponseEncoder func(ctx context.Context, payload interface{}) interface{}

ResponseEncoder encode payload to response body

type StatusCodeError

type StatusCodeError interface {
	StatusCode() int
}

Jump to

Keyboard shortcuts

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