httpin

package module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2021 License: MIT Imports: 9 Imported by: 46

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")
	ErrDuplicateTypeDecoder = errors.New("duplicate type decoder")
	ErrNilTypeDecoder       = errors.New("nil type decoder")
	ErrDuplicateExecutor    = errors.New("duplicate executor")
	ErrNilExecutor          = errors.New("nil executor")
)

Functions ¶

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 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 RegisterTypeDecoder ¶ added in v0.2.2

func RegisterTypeDecoder(typ reflect.Type, decoder TypeDecoder)

RegisterTypeDecoder registers a specific type decoder. Panics on conflicts.

func ReplaceDirectiveExecutor ¶

func ReplaceDirectiveExecutor(name string, exe DirectiveExecutor)

ReplaceDirectiveExecutor works like RegisterDirectiveExecutor without panic on duplicate names.

func ReplaceTypeDecoder ¶ added in v0.2.2

func ReplaceTypeDecoder(typ reflect.Type, decoder TypeDecoder)

ReplaceTypeDecoder replaces a specific type decoder.

func UseGorillaMux ¶

func UseGorillaMux(executor string, fnVars MuxVarsFunc)

UseGorillaMux registers a new directive executor which can extract path variables from the URL. Which works as an accompany to gorilla's mux package.

Example:

UseGorillaMux("path", mux.Vars)

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

func WithErrorStatusCode ¶

func WithErrorStatusCode(code int) option

WithErrorStatusCode configures the HTTP status code sent to the client when decoding a request failed. Which is used in the `NewInput` middleware. The default value is 422.

Types ¶

type ContextKey ¶

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

	// Set this context value to true to indicate that the field has been set.
	// When multiple executors were applied to a field, if the field value were set by
	// an executor, the latter executors may skip running by consulting this context value.
	FieldSet
)

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 Engine ¶ added in v0.2.1

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

Engine holds the information on how to decode a request to an instance of a concrete struct type.

func New ¶

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

New builds an HTTP request decoder for the specified struct type with custom options.

func (*Engine) Decode ¶ added in v0.2.1

func (e *Engine) Decode(req *http.Request) (interface{}, error)

Decode decodes an HTTP request to a struct instance.

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 TypeDecoder ¶ added in v0.2.2

type TypeDecoder = internal.TypeDecoder

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

type TypeDecoderFunc ¶ added in v0.2.2

type TypeDecoderFunc = internal.TypeDecoderFunc

TypeDecoderFunc is an adaptor to allow the use of ordinary functions as httpin TypeDecoders.

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