1
0
mirror of https://github.com/ggicci/httpin.git synced 2024-11-28 08:49:05 +02:00
🍡 HTTP Input for Go - Decode an HTTP request into a custom struct 📢 the encoding feature will come soon, see branch feat/encoder https://ggicci.github.io/httpin/
Go to file
2021-05-08 22:48:43 +08:00
.github/workflows refactor: update ci scripts 2021-04-23 11:26:31 +08:00
internal chore: up README and more cover tests 2021-05-08 22:46:16 +08:00
patch refactor: add internal package to reduce public API surface 2021-05-08 15:27:37 +08:00
.gitignore Initial commit 2021-04-13 10:15:37 +08:00
body_test.go refactor: move httpin_test package to httpin 2021-04-29 11:07:43 +08:00
body.go refactor: add internal package to reduce public API surface 2021-05-08 15:27:37 +08:00
chain_test.go feat: add core option and tests for the middleware 2021-05-08 18:18:57 +08:00
chain.go feat: add core option and tests for the middleware 2021-05-08 18:18:57 +08:00
debug.go feat: debug switch, default UTC time and up tests 2021-05-06 16:06:55 +08:00
decoders.go chore: up README and more cover tests 2021-05-08 22:46:16 +08:00
directives_test.go feat: can replace a directive executor 2021-05-08 15:56:43 +08:00
directives.go chore: up README and more cover tests 2021-05-08 22:46:16 +08:00
errors.go refactor: improve json format of InvalidFieldError 2021-05-08 17:03:55 +08:00
form.go refactor: add internal package to reduce public API surface 2021-05-08 15:27:37 +08:00
go.mod feat: add core option and tests for the middleware 2021-05-08 18:18:57 +08:00
go.sum feat: add core option and tests for the middleware 2021-05-08 18:18:57 +08:00
httpin_test.go refactor: improve json format of InvalidFieldError 2021-05-08 17:03:55 +08:00
httpin.go feat: add core option and tests for the middleware 2021-05-08 18:18:57 +08:00
LICENSE Initial commit 2021-04-13 10:15:37 +08:00
Makefile refactor: update ci scripts 2021-04-23 11:26:31 +08:00
options_test.go feat: add core option and tests for the middleware 2021-05-08 18:18:57 +08:00
options.go feat: add core option and tests for the middleware 2021-05-08 18:18:57 +08:00
README.md chore(docs): update README 2021-05-08 22:48:43 +08:00
required.go refactor: add internal package to reduce public API surface 2021-05-08 15:27:37 +08:00
resolver_test.go feat: implement form and header extractors in directives 2021-04-30 19:10:07 +08:00
resolver.go refactor: improve json format of InvalidFieldError 2021-05-08 17:03:55 +08:00

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:

  • 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 as its arguments.

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

There are several builtin directive executors, e.g. form, header, required, etc. See the 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
}

Seconds, register it to httpin:

httpin.RegisterDirectiveExecutor("to_lowercase", MyTrimDirective)

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

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