.github/workflows | ||
internal | ||
patch | ||
.gitignore | ||
body_test.go | ||
body.go | ||
chain_test.go | ||
chain.go | ||
debug.go | ||
decoders.go | ||
directives_test.go | ||
directives.go | ||
errors.go | ||
form.go | ||
go.mod | ||
go.sum | ||
httpin_test.go | ||
httpin.go | ||
LICENSE | ||
Makefile | ||
options_test.go | ||
options.go | ||
README.md | ||
required.go | ||
resolver_test.go | ||
resolver.go |
httpin
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) |
---|---|
|
|
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
andheader
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 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.