2021-04-13 07:33:04 +02:00
|
|
|
# httpin
|
|
|
|
|
2021-04-22 12:45:15 +02:00
|
|
|
[![codecov](https://codecov.io/gh/ggicci/httpin/branch/main/graph/badge.svg?token=RT61L9ngHj)](https://codecov.io/gh/ggicci/httpin)
|
|
|
|
|
2021-05-08 10:15:26 +02:00
|
|
|
HTTP Input for Go - Decode an HTTP request into a custom struct
|
|
|
|
|
|
|
|
**Define your input struct and then fetch your data!**
|
2021-04-29 05:55:02 +02:00
|
|
|
|
2021-05-08 08:07:09 +02:00
|
|
|
## Quick View
|
2021-04-29 05:55:02 +02:00
|
|
|
|
2021-05-08 08:07:09 +02:00
|
|
|
<table>
|
|
|
|
<tr>
|
2021-05-08 10:15:26 +02:00
|
|
|
<th>BEFORE (use net/http)</th>
|
|
|
|
<th>AFTER (use httpin)</th>
|
2021-05-08 08:07:09 +02:00
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
2021-04-29 05:55:02 +02:00
|
|
|
|
2021-05-08 08:07:09 +02:00
|
|
|
```go
|
|
|
|
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.
|
|
|
|
}
|
|
|
|
```
|
2021-04-29 05:55:02 +02:00
|
|
|
|
2021-05-08 08:07:09 +02:00
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
|
|
|
|
```go
|
|
|
|
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) {
|
2021-05-08 09:27:37 +02:00
|
|
|
interfaceInput, err := httpin.New(ListUsersInput{}).Decode(r)
|
2021-05-08 08:07:09 +02:00
|
|
|
if err != nil {
|
|
|
|
// err can be *httpin.InvalidField
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
input := interfaceInput.(*ListUsersInput)
|
|
|
|
// do sth.
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
- [x] Decode from HTTP query, i.e. `http.Request.Form`
|
|
|
|
- [x] Decode from HTTP headers, e.g. `http.Request.Header`
|
|
|
|
- [x] Builtin decoders for basic types, e.g. `bool`, `int`, `int64`, `float32`, `time.Time`, ... [full list](./decoders.go)
|
|
|
|
- [x] Decode one field by inspecting multiple keys one by one in the same source
|
|
|
|
- [x] Decode one field from multiple sources, e.g. both query and headers
|
|
|
|
- [ ] Customize decoders for user defined types
|
|
|
|
- [x] Define input struct with embedded struct fields
|
|
|
|
- [x] Tag one field as **required**
|
|
|
|
- [ ] Builtin encoders for basic types
|
|
|
|
- [ ] Customize encoders for user defined types
|
|
|
|
|
|
|
|
## Sample User Defined Input Structs
|
2021-04-29 05:55:02 +02:00
|
|
|
|
|
|
|
```go
|
|
|
|
type Authorization struct {
|
2021-05-08 08:07:09 +02:00
|
|
|
// Decode from multiple sources, the former with higher priority
|
2021-05-06 10:06:55 +02:00
|
|
|
Token string `in:"form=access_token,header=x-api-token"`
|
2021-04-29 05:55:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type Pagination struct {
|
2021-05-08 08:07:09 +02:00
|
|
|
Page int `in:"form=page"`
|
|
|
|
|
|
|
|
// Decode from multiple keys in the same source, the former with higher priority
|
2021-05-06 10:06:55 +02:00
|
|
|
PerPage int `in:"form=per_page,page_size"`
|
2021-04-29 05:55:02 +02:00
|
|
|
}
|
|
|
|
|
2021-05-08 08:07:09 +02:00
|
|
|
type ListUsersInput struct {
|
2021-05-06 10:06:55 +02:00
|
|
|
Gender string `in:"form=gender"`
|
|
|
|
AgeRange []int `in:"form=age_range"`
|
|
|
|
IsMember bool `in:"form=is_member"`
|
2021-04-29 05:55:02 +02:00
|
|
|
|
2021-05-08 08:07:09 +02:00
|
|
|
Pagination // Embedded field works
|
|
|
|
Authorization // Embedded field works
|
2021-04-29 05:55:02 +02:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2021-05-08 08:07:09 +02:00
|
|
|
## Advanced - Use Middleware
|
2021-04-29 05:55:02 +02:00
|
|
|
|
2021-05-08 08:11:26 +02:00
|
|
|
First, set up the middleware for your handlers. We recommend using [alice](https://github.com/justinas/alice) to chain your HTTP middleware functions.
|
2021-04-29 05:55:02 +02:00
|
|
|
|
|
|
|
```go
|
2021-05-08 08:07:09 +02:00
|
|
|
func init() {
|
|
|
|
mux.Handle("/users", alice.New(
|
|
|
|
httpin.NewInput(ListUsersInput{}),
|
|
|
|
).ThenFunc(ListUsers)).Methods("GET")
|
|
|
|
}
|
2021-04-29 05:55:02 +02:00
|
|
|
```
|
|
|
|
|
2021-05-08 08:07:09 +02:00
|
|
|
Second, fetch your input with **only one line** of code.
|
2021-04-29 05:55:02 +02:00
|
|
|
|
|
|
|
```go
|
2021-05-08 08:07:09 +02:00
|
|
|
func ListUsers(rw http.ResponseWriter, r *http.Request) {
|
|
|
|
input := r.Context().Value(httpin.Input).(*UserQuery)
|
|
|
|
// do sth.
|
2021-04-29 05:55:02 +02:00
|
|
|
}
|
|
|
|
```
|