/httpin

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

Primary LanguageGoMIT LicenseMIT

httpin

codecov

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 page
		return
	}

	perPage, err := strconv.ParseInt(r.FormValue("per_page"), 10, 64)
	if err != nil {
		// ... invalid per_page
		return
	}

	isVip, _ := strconv.ParseBool(r.FormValue("is_vip"))

	// do sth.
}
type ListUsersInput struct {
	Page    int  `in:"form=page"`
	PerPage int  `in:"form=per_page"`
	IsVip   bool `in:"form=is_vip"`
}

func ListUsers(rw http.ResponseWriter, r *http.Request) {
	interfaceInput, err := httpin.New(ListUsersInput{}).Decode(r)
	if err != nil {
		// err can be *httpin.InvalidField
		return
	}

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

Features

  • Builtin directive form to decode a field from HTTP query, i.e. http.Request.Form
  • Builtin directive header to decode a field from HTTP headers, e.g. 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
  • Decode a field from multiple sources, e.g. both query and headers
  • Register or replace decoders for both builtin basic types and custom types
  • Define input struct with embedded struct fields
  • Builtin directive required to tag a field as required
  • Builtin encoders for basic types
  • Register or replace encoders for both builtin basic types and custom types
  • Register custom directive executors to extend the field resolving abilities, see directive required as an example and think about implementing your own directives like trim, to_lowercase, base58_to_int, etc.

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
}

Advanced

Use middleware handlers to reduce much more trivial code

First, set up the middleware for your handlers. 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).(*UserQuery)
	// do sth.
}

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.