httpin

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2021 License: MIT Imports: 9 Imported by: 30

README ¶

httpin

Go Workflow codecov Go Reference

HTTP Input for Go - Decode an HTTP request into a custom struct

Define the struct for your input and then fetch your data!

Quick View

BEFORE (use net/http) AFTER (use httpin)
func ListUsers(rw http.ResponseWriter, r *http.Request) {
	page, err := strconv.ParseInt(r.FormValue("page"), 10, 64)
	if err != nil {
		// Invalid parameter: page.
		return
	}
	perPage, err := strconv.ParseInt(r.FormValue("per_page"), 10, 64)
	if err != nil {
		// Invalid parameter: per_page.
		return
	}
	isMember, err := strconv.ParseBool(r.FormValue("is_member"))
	if err != nil {
		// Invalid parameter: is_member.
		return
	}

	// Do sth.
}
type ListUsersInput struct {
	Page     int  `in:"form=page"`
	PerPage  int  `in:"form=per_page"`
	IsMember bool `in:"form=is_member"`
}

func ListUsers(rw http.ResponseWriter, r *http.Request) {
	inputInterface, err := httpin.New(ListUsersInput{}).Decode(r)
	if err != nil {
		// Error occurred, `err` can be type of *httpin.InvalidFieldError
		// Do sth.
		return
	}

	input := interfaceInput.(*ListUsersInput)
	// Do sth.
}

Features

  • Builtin directive form to decode a field from HTTP query (URL params), i.e. http.Request.Form
  • Builtin directive header to decode a field from HTTP headers, i.e. http.Request.Header
  • Builtin decoders used by form and header directives for basic types, e.g. bool, int, int64, float32, time.Time, ... full list
  • Decode a field by inspecting a set of keys from the same source, e.g. in:"form=per_page,page_size"
  • Decode a field from multiple sources, e.g. both query and headers, in:"form=access_token;header=x-api-token"
  • Register custom type decoders by implementing httpin.Decoder interface
  • Compose an input struct by embedding struct fields
  • Builtin directive required to tag a field as required
  • Register custom directive executors to extend the ability of field resolving, see directive required as an example and think about implementing your own directives like trim, to_lowercase, base58_to_int, etc.
  • Easily integrating with popular Go web frameworks and packages

Sample User Defined Input Structs

type Authorization struct {
	// Decode from multiple sources, the former with higher priority
	Token string `in:"form=access_token;header=x-api-token;required"`
}

type Pagination struct {
	Page int `in:"form=page"`

	// Decode from multiple keys in the same source, the former with higher priority
	PerPage int `in:"form=per_page,page_size"`
}

type ListUsersInput struct {
	Gender   string `in:"form=gender"`
	AgeRange []int  `in:"form=age_range"`
	IsMember bool   `in:"form=is_member"`

	Pagination    // Embedded field works
	Authorization // Embedded field works
}

Integrate with Go Native http.Handler (Use Middleware)

First, set up the middleware for your handlers (bind Input vs. Handler). We recommend using alice to chain your HTTP middleware functions.

func init() {
	http.Handle("/users", alice.New(
		httpin.NewInput(ListUsersInput{}),
	).ThenFunc(ListUsers))
}

Second, fetch your input with only ONE LINE of code.

func ListUsers(rw http.ResponseWriter, r *http.Request) {
	input := r.Context().Value(httpin.Input).(*ListUsersInput)

	// Do sth.
}
Frameworks
Components

Advanced

🔥 Extend httpin by adding custom directives

Know the concept of a Directive:

type Authorization struct {
	Token string `in:"form=access_token,token;header=x-api-token;required"`
	                  ^---------------------^ ^----------------^ ^------^
	                            d1                    d2            d3
}

There are three directives above, separated by semicolons (;):

  • d1: form=access_token,token
  • d2: header=x-api-token
  • d3: required

A directive consists of two parts separated by an equal sign (=). The left part is the name of an executor who was designed to run this directive. The right part is a list of arguments separated by commas (,) which will be passed to the corresponding executor at runtime.

For instance, form=access_token,token, here form is the name of the executor, and access_token,token is the argument list which will be parsed as []string{"access_token", "token"}.

There are several builtin directive executors, e.g. form, header, required, ... full list. And you can define your own by:

First, create a directive executor by implementing the httpin.DirectiveExecutor interface:

func toLower(ctx *DirectiveContext) error {
	if ctx.ValueType.Kind() != reflect.String {
		return errors.New("not a string")
	}

	currentValue := ctx.Value.Elem().String()
	newValue := strings.ToLower(currentValue)
	ctx.Value.Elem().SetString(newValue)
	return nil
}

// Adapt toLower to httpin.DirectiveExecutor.
var MyLowercaseDirectiveExecutor = httpin.DirectiveExecutorFunc(toLower)

Seconds, register it to httpin:

httpin.RegisterDirectiveExecutor("to_lowercase", MyLowercaseDirectiveExecutor)

Finally, you can use your own directives in the struct tags:

type Authorization struct {
	Token string `in:"form=token;required;to_lowercase"`
}

The directives will run in the order as they defined in the struct tag.

Documentation ¶

Index ¶

Constants ¶

This section is empty.

Variables ¶

View Source
var (
	ErrMissingField         = errors.New("missing required field")
	ErrUnsupporetedType     = errors.New("unsupported type")
	ErrUnregisteredExecutor = errors.New("unregistered executor")
)

Functions ¶

func DebugOff ¶

func DebugOff()

func DebugOn ¶

func DebugOn()

func ExtractFromKVS ¶

func ExtractFromKVS(ctx *DirectiveContext, kvs map[string][]string, isHeaderKey bool) error

func New ¶

func New(inputStruct interface{}, opts ...option) (*core, error)

func NewInput ¶

func NewInput(inputStruct interface{}, opts ...option) func(http.Handler) http.Handler

NewInput creates a "Middleware Constructor" for making a chain, which acts as a list of http.Handler constructors. We recommend using https://github.com/justinas/alice to chain your HTTP middleware functions and the app handler.

func RegisterDecoder ¶

func RegisterDecoder(typ reflect.Type, decoder Decoder)

RegisterDecoder registers a decoder to decode the specific type. Panics on conflicts.

func RegisterDirectiveExecutor ¶

func RegisterDirectiveExecutor(name string, exe DirectiveExecutor)

RegisterDirectiveExecutor registers a named executor globally, which implemented the DirectiveExecutor interface. Will panic if the name were taken or nil executor.

func ReplaceDecoder ¶

func ReplaceDecoder(typ reflect.Type, decoder Decoder)

ReplaceDecoder replaces a decoder to decode the specific type.

func ReplaceDirectiveExecutor ¶

func ReplaceDirectiveExecutor(name string, exe DirectiveExecutor)

ReplaceDirectiveExecutor works like RegisterDirectiveExecutor without panic on duplicate names.

func UseGorillaMux ¶

func UseGorillaMux(executor string, fnVars MuxVarsFunc)

UseGorillaMux registers a new directive executor which can extract path variables from the URL.

Example: UseGorillaMux("path", mux.Vars)

type GetUserInput struct {
   UserID `httpin:"path=user_id"`
}

func WithErrorStatusCode ¶

func WithErrorStatusCode(code int) option

Types ¶

type ContextKey ¶

type ContextKey int
const (
	Input ContextKey = iota // the primary key to get the input object in the context injected by httpin

	FieldSet
)

type Decoder ¶

type Decoder = internal.Decoder

Decoder is the interface implemented by types that can decode bytes to themselves.

type DecoderFunc ¶

type DecoderFunc = internal.DecoderFunc

DecoderFunc is an adaptor to allow the use of ordinary functions as httpin decoders.

type DirectiveContext ¶

type DirectiveContext struct {
	ValueType reflect.Type
	Value     reflect.Value
	Request   *http.Request
	Context   context.Context
	// contains filtered or unexported fields
}

DirectiveContext holds essential information about the field being resolved and the active HTTP request. Working as the context in a directive executor.

func (*DirectiveContext) DeliverContextValue ¶

func (c *DirectiveContext) DeliverContextValue(key, value interface{})

DeliverContextValue binds a value to the specified key in the context. And it will be delivered among the executors in the same field resolver.

func (*DirectiveContext) Execute ¶

func (d *DirectiveContext) Execute(ctx *DirectiveContext) error

Execute locates the executor and runs it with the specified context.

type DirectiveExecutor ¶

type DirectiveExecutor interface {
	Execute(*DirectiveContext) error
}

DirectiveExecutor is the interface implemented by a "directive executor".

type DirectiveExecutorFunc ¶

type DirectiveExecutorFunc func(*DirectiveContext) error

DirectiveExecutorFunc is an adpator to allow to use of ordinary functions as httpin.DirectiveExecutor.

func (DirectiveExecutorFunc) Execute ¶

Execute calls f(ctx).

type FieldResolver ¶

type FieldResolver struct {
	Type       reflect.Type
	Field      reflect.StructField
	Path       []string
	Directives []*directive
	Fields     []*FieldResolver
}

type InvalidFieldError ¶

type InvalidFieldError struct {
	// Field is the name of the field.
	Field string `json:"field"`

	// Source is the directive which causes the error.
	// e.g. form, header, required, etc.
	Source string `json:"source"`

	// Value is the input data.
	Value interface{} `json:"value"`

	ErrorMessage string `json:"error"`
	// contains filtered or unexported fields
}

func (*InvalidFieldError) Error ¶

func (f *InvalidFieldError) Error() string

func (*InvalidFieldError) Unwrap ¶

func (f *InvalidFieldError) Unwrap() error

type MuxVarsFunc ¶

type MuxVarsFunc func(*http.Request) map[string]string

type UnsupportedTypeError ¶

type UnsupportedTypeError struct {
	Type reflect.Type
}

func (UnsupportedTypeError) Error ¶

func (e UnsupportedTypeError) Error() string

func (UnsupportedTypeError) Unwrap ¶

func (e UnsupportedTypeError) Unwrap() error

Directories ¶

Path Synopsis

Jump to

Keyboard shortcuts

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