httpin

package module
v0.14.2 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2023 License: MIT Imports: 15 Imported by: 11

README

httpin logo

httpin - HTTP Input for Go

Decode an HTTP request into a custom struct

Core Features

httpin helps you easily decoding HTTP request data from

  • Query parameters, e.g. ?name=john&is_member=true
  • Headers, e.g. Authorization: xxx
  • Form data, e.g. username=john&password=******
  • JSON/XML Body, e.g. POST {"name":"john"}
  • Path variables, e.g. /users/{username}
  • File uploads

You only need to define a struct to receive/bind data from an HTTP request, without writing any parsing stuff code by yourself.

How to use?

type ListUsersInput struct {
	Token    string `in:"query=access_token;header=x-access-token"`
	Page     int    `in:"query=page;default=1"`
	PerPage  int    `in:"query=per_page;default=20"`
	IsMember bool   `in:"query=is_member"`
}

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

	if input.IsMember {
		// Do sth.
	}
	// Do sth.
}

httpin is:

Why this package?

Compared with using net/http package
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.
}
Benefits Before (use net/http package) After (use ggicci/httpin package)
⌛️ Developer Time 😫 Expensive (too much parsing stuff code) 🚀 Faster (define the struct for receiving input data and leave the parsing job to httpin)
♻️ Code Repetition Rate 😞 High 😍 Lower
📖 Code Readability 😟 Poor 🤩 Highly readable
🔨 Maintainability 😡 Poor 🥰 Highly maintainable

Alternatives and Similars

Documentation

Overview

Package httpin helps decoding an HTTP request to a custom struct by binding data with querystring (query params), HTTP headers, form data, JSON/XML payloads, URL path params, and file uploads (multipart/form-data).

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")
	ErrDuplicateNamedDecoder = errors.New("duplicate named decoder")
	ErrNilDecoder            = errors.New("nil decoder")
	ErrInvalidDecoder        = errors.New("invalid decoder")
	ErrReservedExecutorName  = errors.New("reserved executor name")
	ErrUnknownBodyType       = errors.New("unknown body type")
	ErrNilErrorHandler       = errors.New("nil error handler")
	ErrMaxMemoryTooSmall     = errors.New("max memory too small")
	ErrNilFile               = errors.New("nil file")
	ErrDuplicateBodyDecoder  = errors.New("duplicate body decoder")
	ErrMissingDecoderName    = errors.New("missing decoder name")
	ErrDecoderNotFound       = errors.New("decoder not found")
	ErrValueTypeMismatch     = errors.New("value type mismatch")
)

Functions

func Decode added in v0.11.0

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

Decode decodes an HTTP request to the given input struct. The input must be a pointer to a struct instance. For example:

input := &InputStruct{}
if err := Decode(req, &input); err != nil { ... }

input is now populated with data from the request.

func NewInput

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

NewInput creates a "Middleware". A middleware is a function that takes a http.Handler and returns another http.Handler.

The middleware created by NewInput is to add the decoding function to an existing http.Handler. This functionality will decode the HTTP request and put the decoded struct instance to the request's context. So that the next hop can get the decoded struct instance from the request's context.

We recommend using https://github.com/justinas/alice to chain your middlewares. If you're using some popular web frameworks, they may have already provided a middleware chaining mechanism.

func RegisterBodyDecoder added in v0.9.0

func RegisterBodyDecoder(bodyType string, decoder BodyDecoder)

RegisterBodyDecoder registers a new body decoder. Panic if the body type is already registered.

func init() {
    RegisterBodyDecoder("yaml", &myYAMLBodyDecoder{})
}

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 RegisterFileTypeDecoder added in v0.12.0

func RegisterFileTypeDecoder[T any](decoder Decoder[*multipart.FileHeader])

RegisterFileTypeDecoder registers a FileTypeDecoder. The decoder takes in a *multipart.FileHeader (when uploading files from an HTTP request) and decodes it to value of type T. Panics on conflict types and nil decoders.

NOTE: the decoder returns the decoded value as interface{}. For best practice, the underlying type of the decoded value should be T, even returning *T also works. If the real returned value were not T or *T, the decoder will return an error (ErrValueTypeMismatch) while decoding.

func RegisterNamedDecoder added in v0.10.0

func RegisterNamedDecoder[T any](name string, decoder interface{})

RegisterNamedDecoder registers a decoder by name. Panics on conflict names and invalid decoders. The decoder can be a ValueTypeDecoder or a FileTypeDecoder. It decodes the input value to a value of type T. Use the *decoder directive* to override the decoder of a struct field:

RegisterNamedDecoder[time.Time]("x_time", DecoderFunc[string](decodeTimeInXFormat))
type Input struct {
    // httpin will use the decoder registered above, instead of the builtin decoder for time.Time.
    Time time.Time `in:"query:time;decoder=x_time"`
}

Visit https://ggicci.github.io/httpin/directives/decoder for more details.

func RegisterValueTypeDecoder added in v0.12.0

func RegisterValueTypeDecoder[T any](decoder Decoder[string])

RegisterValueTypeDecoder registers a ValueTypeDecoder. The decoder takes in a string value and decodes it to value of type T. Panics on conflict types and nil decoders.

NOTE: the decoder returns the decoded value as interface{}. For best practice, the underlying type of the decoded value should be T, even returning *T also works. If the real returned value were not T or *T, the decoder will return an error (ErrValueTypeMismatch) while decoding.

func ReplaceBodyDecoder added in v0.9.0

func ReplaceBodyDecoder(bodyType string, decoder BodyDecoder)

ReplaceBodyDecoder replaces or add the body decoder of the specified type. Which is useful when you want to override the default body decoder. For example, the default JSON decoder is borrowed from encoding/json. You can replace it with your own implementation, e.g. json-iterator/go.

func init() {
    ReplaceBodyDecoder("json", &myJSONBodyDecoder{})
}

func ReplaceDefaultErrorHandler added in v0.6.1

func ReplaceDefaultErrorHandler(custom ErrorHandler)

ReplaceDefaultErrorHandler replaces the default error handler with the given custom error handler. The default error handler will be used in the http.Handler that decoreated by the middleware created by NewInput().

func ReplaceDirectiveExecutor

func ReplaceDirectiveExecutor(name string, exe DirectiveExecutor)

ReplaceDirectiveExecutor works like RegisterDirectiveExecutor without panic on duplicate names.

func ReplaceFileTypeDecoder added in v0.12.0

func ReplaceFileTypeDecoder[T any](decoder Decoder[*multipart.FileHeader])

ReplaceFileTypeDecoder works similar to RegisterFileTypeDecoder. But it replaces the decoder if there is a conflict. It still panics on nil decoders.

func ReplaceNamedDecoder added in v0.10.0

func ReplaceNamedDecoder[T any](name string, decoder interface{})

ReplaceNamedDecoder works similar to RegisterNamedDecoder. But it replaces the decoder if there is a conflict. It still panics on invalid decoders.

func ReplaceValueTypeDecoder added in v0.12.0

func ReplaceValueTypeDecoder[T any](decoder Decoder[string])

ReplaceValueTypeDecoder works similar to RegisterValueTypeDecoder. But it replaces the decoder if there is a conflict. It still panics on nil decoders.

func UseGochiURLParam added in v0.3.0

func UseGochiURLParam(directive string, fn GochiURLParamFunc)

UseGochiURLParam registers a directive executor which can extract values from `chi.URLParam`, i.e. path variables. https://ggicci.github.io/httpin/integrations/gochi

Usage:

func init() {
    httpin.UseGochiURLParam("path", chi.URLParam)
}

func UseGorillaMux

func UseGorillaMux(executor string, fnVars GorillaMuxVarsFunc)

UseGorillaMux registers a new directive executor which can extract values from `mux.Vars`, i.e. path variables. https://ggicci.github.io/httpin/integrations/gorilla

Usage:

func init() {
    httpin.UseGorillaMux("path", mux.Vars)
}

Types

type BodyDecoder added in v0.9.0

type BodyDecoder interface {
	Decode(src io.Reader, dst interface{}) error
}

BodyDecoder decodes the request body into the specified object. Common body types are: json, xml, yaml, and others.

type ContextKey

type ContextKey int
const (
	// Input is the key to get the input object from Request.Context() injected by httpin. e.g.
	//
	//     input := r.Context().Value(httpin.Input).(*InputStruct)
	Input ContextKey = iota

	// RequestValue is the key to get the HTTP request value (of *http.Request)
	// from DirectiveRuntime.Context. The HTTP request value is injected by
	// httpin to the context of DirectiveRuntime before executing the directive.
	// See Core.Decode() for more details.
	RequestValue

	// CustomDecoder is the key to get the custom decoder for a field from
	// Resolver.Context. Which is specified by the "decoder" directive.
	// During resolver building phase, the "decoder" directive will be removed
	// from the resolver, and the targeted decoder by name will be put into
	// Resolver.Context with this key. e.g.
	//
	//    type GreetInput struct {
	//        Message string `httpin:"decoder=custom"`
	//    }
	// For the above example, the decoder named "custom" will be put into the
	// resolver of Message field with this key.
	CustomDecoder

	// FieldSet is used by executors to tell whether a field has been set. When
	// multiple executors were applied to a field, if the field value were set
	// by a former executor, the latter executors MAY skip running by consulting
	// this context value.
	FieldSet
)

type Core added in v0.12.0

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

Core is the core of httpin. It holds the resolver of a specific struct type. Who is responsible for decoding an HTTP request to an instance of such struct type.

func New

func New(inputStruct interface{}, opts ...Option) (*Core, error)

New creates a new Core instance, which holds the resolver of the inputStruct.

  • Use Core.Decode() to decode an HTTP request to an instance of the inputStruct.
  • Use NewInput() to create an HTTP middleware.

func (*Core) Decode added in v0.12.0

func (c *Core) Decode(req *http.Request) (interface{}, error)

Decode decodes an HTTP request to a struct instance. The return value is a pointer to the input struct. For example:

New(&Input{}).Decode(req) -> *Input
New(Input{}).Decode(req) -> *Input

type DataSource added in v0.12.0

type DataSource interface{ string | *multipart.FileHeader }

DataSource is the type of the input data. It can be string or *multipart.FileHeader.

  • string: when the input data is from a querystring, form, or header.
  • *multipart.FileHeader: when the input data is from a file upload.

type Decoder

type Decoder[DT DataSource] interface {
	Decode(value DT) (interface{}, error)
}

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

type DecoderFunc

type DecoderFunc[DT DataSource] func(value DT) (interface{}, error)

DecoderFunc is a function that implements Decoder[DT]. It can be used to turn a function into a Decoder[DT]. For instance:

func decodeInt(value string) (interface{}, error) { ... }
myIntDecoder := DecoderFunc[string](decodeInt)

func (DecoderFunc[DT]) Decode added in v0.13.0

func (fn DecoderFunc[DT]) Decode(value DT) (interface{}, error)

type Directive added in v0.4.0

type Directive = owl.Directive

type DirectiveExecutor

type DirectiveExecutor = owl.DirectiveExecutor

type DirectiveExecutorFunc

type DirectiveExecutorFunc = owl.DirectiveExecutorFunc

type DirectiveRuntime added in v0.12.0

type DirectiveRuntime = owl.DirectiveRuntime

type ErrorHandler added in v0.6.0

type ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error)

ErrorHandler is the type of custom error handler. The error handler is used by the http.Handler that created by NewInput() to handle errors during decoding the HTTP request.

type File added in v0.7.0

type File struct {
	multipart.File
	Header *multipart.FileHeader
	Valid  bool
}

type FileTypeDecoder added in v0.7.0

type FileTypeDecoder = Decoder[*multipart.FileHeader]

FileTypeDecoder is the interface implemented by types that can decode a *multipart.FileHeader to themselves.

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"`

	// Key is the key to get the input data from the source.
	Key string `json:"key"`

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

	// ErrorMessage is the string representation of `internalError`.
	ErrorMessage string `json:"error"`
	// contains filtered or unexported fields
}

func NewInvalidFieldError added in v0.12.0

func NewInvalidFieldError(err *owl.ResolveError) *InvalidFieldError

func (*InvalidFieldError) Error

func (e *InvalidFieldError) Error() string

func (*InvalidFieldError) Unwrap

func (e *InvalidFieldError) Unwrap() error

type Option added in v0.6.0

type Option func(*Core) error

func WithErrorHandler added in v0.6.0

func WithErrorHandler(custom ErrorHandler) Option

WithErrorHandler overrides the default error handler.

func WithMaxMemory added in v0.7.0

func WithMaxMemory(maxMemory int64) Option

WithMaxMemory overrides the default maximum memory size (32MB) when reading the request body. See https://pkg.go.dev/net/http#Request.ParseMultipartForm for more details.

type ValueTypeDecoder added in v0.7.0

type ValueTypeDecoder = Decoder[string]

ValueTypeDecoder is the interface implemented by types that can decode a string to themselves. Take querystring as an example, the decoder takes in a single string value and decodes it to value of type T.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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