httpin

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2021 License: MIT Imports: 10 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) {
	input := r.Context().Value(httpin.Input).(*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
  • Builtin directive body to parse HTTP request body as JSON or XML
  • 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

Example (Use http.Handler)

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

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
}

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.
}

Parse HTTP Request Body

There're two ways of parsing HTTP request body into your input struct.

Parse HTTP request body into the input struct (body -> input)

Embed one of our body decoder annotations into your input struct:

// POST /users with JSON body
type CreateUserInput struct {
	httpin.JSONBody        // annotation
	Username        string `json:"username"`
	Gender          int    `json:"gender"`
}
  • httpin.JSONBody: parse request body in JSON format
  • httpin.XMLBody: parse request body in XML format
Parse HTTP request body into a field of the input struct (body -> input.field)

Use body directive.

// PATCH /users/:uid with JSON body
type UpdateUserProfileInput struct {
	UserID int64 `in:"path=uid"` // get user id from path variable
	Patch  struct {
		Username string `json:"username"`
		Gender   int    `json:"gender"`
	} `in:"body=json"` // use body=xml to decode in XML format
}

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, nil)

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")
	ErrUnknownBodyType          = errors.New("unknown body type")
	ErrDuplicateAnnotationField = errors.New("duplicate annotation field")
)

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, norm DirectiveNormalizer)

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, norm DirectiveNormalizer)

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 UseGochiURLParam ¶ added in v0.3.0

func UseGochiURLParam(executor string, fn GochiURLParamFunc)

func UseGorillaMux ¶

func UseGorillaMux(executor string, fnVars GorillaMuxVarsFunc)

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

	StopRecursion
)

type Directive ¶ added in v0.4.0

type Directive struct {
	Executor string   // name of the executor
	Argv     []string // argv
}

Directive defines the profile to locate an httpin.DirectiveExecutor instance and drive it with essential arguments.

func (*Directive) Execute ¶ added in v0.4.0

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

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

type DirectiveContext ¶

type DirectiveContext struct {
	Directive
	ValueType reflect.Type
	Value     reflect.Value
	Request   *http.Request
	Context   context.Context
}

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.

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 DirectiveNormalizer ¶ added in v0.4.0

type DirectiveNormalizer interface {
	Normalize(*Directive) error
}

type DirectiveNormalizerFunc ¶ added in v0.4.0

type DirectiveNormalizerFunc func(*Directive) error

DirectiveNormalizerFunc is an adaptor to allow to use of ordinary functions as httpin.DirectiveNormalizer.

func (DirectiveNormalizerFunc) Normalize ¶ added in v0.4.0

func (f DirectiveNormalizerFunc) Normalize(dir *Directive) error

Normalize calls f(dir).

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 GochiURLParamFunc ¶ added in v0.3.0

type GochiURLParamFunc func(r *http.Request, key string) string

GochiURLParamFunc is chi.URLParam

type GorillaMuxVarsFunc ¶ added in v0.3.0

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

GorillaMuxVarsFunc is mux.Vars

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 JSONBody ¶ added in v0.4.0

type JSONBody struct{}

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

type XMLBody ¶ added in v0.4.0

type XMLBody struct{}

Directories ¶

Path Synopsis

Jump to

Keyboard shortcuts

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