1
0
mirror of https://github.com/ggicci/httpin.git synced 2025-02-15 13:33:03 +02:00
httpin/README.md

199 lines
5.9 KiB
Markdown
Raw Normal View History

2021-04-13 13:33:04 +08:00
# httpin
2021-04-22 18:45:15 +08:00
[![codecov](https://codecov.io/gh/ggicci/httpin/branch/main/graph/badge.svg?token=RT61L9ngHj)](https://codecov.io/gh/ggicci/httpin)
2021-05-08 16:15:26 +08:00
HTTP Input for Go - Decode an HTTP request into a custom struct
2021-05-08 16:34:38 +08:00
**Define the struct for your input and then fetch your data!**
2021-04-29 11:55:02 +08:00
2021-05-08 14:07:09 +08:00
## Quick View
2021-04-29 11:55:02 +08:00
2021-05-08 14:07:09 +08:00
<table>
<tr>
2021-05-08 16:15:26 +08:00
<th>BEFORE (use net/http)</th>
<th>AFTER (use httpin)</th>
2021-05-08 14:07:09 +08:00
</tr>
<tr>
<td>
2021-04-29 11:55:02 +08:00
2021-05-08 14:07:09 +08:00
```go
func ListUsers(rw http.ResponseWriter, r *http.Request) {
page, err := strconv.ParseInt(r.FormValue("page"), 10, 64)
if err != nil {
2021-05-10 15:35:02 +08:00
// Invalid parameter: page.
2021-05-08 14:07:09 +08:00
return
}
perPage, err := strconv.ParseInt(r.FormValue("per_page"), 10, 64)
if err != nil {
2021-05-10 15:35:02 +08:00
// Invalid parameter: per_page.
return
}
isMember, err := strconv.ParseBool(r.FormValue("is_member"))
if err != nil {
// Invalid parameter: is_member.
2021-05-08 14:07:09 +08:00
return
}
2021-05-10 15:35:02 +08:00
// Do sth.
2021-05-08 14:07:09 +08:00
}
```
2021-04-29 11:55:02 +08:00
2021-05-08 14:07:09 +08:00
</td>
2021-05-10 15:36:06 +08:00
<td>
2021-05-08 14:07:09 +08:00
```go
type ListUsersInput struct {
2021-05-10 15:35:02 +08:00
Page int `in:"form=page"`
PerPage int `in:"form=per_page"`
IsMember bool `in:"form=is_member"`
2021-05-08 14:07:09 +08:00
}
func ListUsers(rw http.ResponseWriter, r *http.Request) {
2021-05-10 15:35:02 +08:00
inputInterface, err := httpin.New(ListUsersInput{}).Decode(r)
2021-05-08 14:07:09 +08:00
if err != nil {
2021-05-10 15:35:02 +08:00
// Error occurred, `err` can be type of *httpin.InvalidFieldError
// Do sth.
2021-05-08 14:07:09 +08:00
return
}
input := interfaceInput.(*ListUsersInput)
2021-05-10 15:35:02 +08:00
// Do sth.
2021-05-08 14:07:09 +08:00
}
```
</td>
</tr>
</table>
## Features
2021-05-17 15:16:08 +08:00
- [x] Builtin directive `form` to decode a field from HTTP query (URL params), i.e. `http.Request.Form`
- [x] Builtin directive `header` to decode a field from HTTP headers, i.e. `http.Request.Header`
2021-06-24 14:47:25 +08:00
- [x] Builtin decoders used by `form` and `header` directives for basic types, e.g. `bool`, `int`, `int64`, `float32`, `time.Time`, ... [full list](./internal/decoders.go)
- [x] Decode a field by inspecting a set of keys from the same source, e.g. `in:"form=per_page,page_size"`
- [x] Decode a field from multiple sources, e.g. both query and headers, `in:"form=access_token;header=x-api-token"`
- [x] Register custom type decoders by implementing `httpin.Decoder` interface
- [x] Compose an input struct by embedding struct fields
2021-05-08 16:33:23 +08:00
- [x] Builtin directive `required` to tag a field as **required**
2021-05-17 15:16:08 +08:00
- [x] Register custom directive executors to extend the ability of field resolving, see directive [required](./required.go) as an example and think about implementing your own directives like `trim`, `to_lowercase`, `base58_to_int`, etc.
2021-06-24 14:50:22 +08:00
- [x] Easily integrating with popular Go web frameworks and packages
2021-05-08 14:07:09 +08:00
## Sample User Defined Input Structs
2021-04-29 11:55:02 +08:00
```go
type Authorization struct {
2021-05-08 14:07:09 +08:00
// Decode from multiple sources, the former with higher priority
2021-05-08 16:42:32 +08:00
Token string `in:"form=access_token;header=x-api-token;required"`
2021-04-29 11:55:02 +08:00
}
type Pagination struct {
2021-05-08 14:07:09 +08:00
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"`
2021-04-29 11:55:02 +08:00
}
2021-05-08 14:07:09 +08:00
type ListUsersInput struct {
Gender string `in:"form=gender"`
AgeRange []int `in:"form=age_range"`
IsMember bool `in:"form=is_member"`
2021-04-29 11:55:02 +08:00
2021-05-08 14:07:09 +08:00
Pagination // Embedded field works
Authorization // Embedded field works
2021-04-29 11:55:02 +08:00
}
```
2021-05-25 13:34:26 +08:00
## Integrate with Go Native http.Handler (Use Middleware)
2021-04-29 11:55:02 +08:00
2021-05-10 15:35:02 +08:00
First, set up the middleware for your handlers (**bind Input vs. Handler**). We recommend using [alice](https://github.com/justinas/alice) to chain your HTTP middleware functions.
2021-04-29 11:55:02 +08:00
```go
2021-05-08 14:07:09 +08:00
func init() {
http.Handle("/users", alice.New(
2021-05-08 14:07:09 +08:00
httpin.NewInput(ListUsersInput{}),
).ThenFunc(ListUsers))
2021-05-08 14:07:09 +08:00
}
2021-04-29 11:55:02 +08:00
```
2021-05-10 15:35:02 +08:00
Second, fetch your input with only **ONE LINE** of code.
2021-04-29 11:55:02 +08:00
```go
2021-05-08 14:07:09 +08:00
func ListUsers(rw http.ResponseWriter, r *http.Request) {
2021-05-10 15:38:56 +08:00
input := r.Context().Value(httpin.Input).(*ListUsersInput)
2021-05-10 15:35:02 +08:00
// Do sth.
2021-04-29 11:55:02 +08:00
}
```
2021-05-08 22:46:16 +08:00
2021-06-24 14:50:22 +08:00
## Integrate with Popular Go Web Frameworks and Components
2021-05-25 13:34:26 +08:00
2021-06-24 14:47:25 +08:00
### Frameworks
2021-06-24 14:55:57 +08:00
- [gin-gonic/gin](https://github.com/ggicci/httpin/wiki/Integrate-with-gin)
- [revel/revel](https://github.com/ggicci/httpin/wiki/Integrate-with-revel)
- [gofiber/fiber](https://github.com/ggicci/httpin/wiki/Integrate-with-fiber)
2021-06-24 14:47:25 +08:00
2021-06-24 14:50:22 +08:00
### Components
2021-06-24 14:47:25 +08:00
2021-06-24 14:55:57 +08:00
- [HTTP Router: gorilla/mux](https://github.com/ggicci/httpin/wiki/Integrate-with-gorilla-mux)
2021-05-25 13:34:26 +08:00
## Advanced
2021-05-10 15:35:02 +08:00
### 🔥 Extend `httpin` by adding custom directives
2021-05-08 22:46:16 +08:00
Know the concept of a `Directive`:
```go
type Authorization struct {
Token string `in:"form=access_token,token;header=x-api-token;required"`
^---------------------^ ^----------------^ ^------^
2021-05-08 22:48:43 +08:00
d1 d2 d3
2021-05-08 22:46:16 +08:00
}
```
2021-05-08 22:56:26 +08:00
There are three directives above, separated by semicolons (`;`):
2021-05-08 22:46:16 +08:00
2021-05-08 22:48:43 +08:00
- d1: `form=access_token,token`
- d2: `header=x-api-token`
- d3: `required`
2021-05-08 22:46:16 +08:00
2021-05-08 22:52:58 +08:00
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.
2021-05-08 22:46:16 +08:00
2021-05-08 22:52:58 +08:00
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"}`.
2021-05-08 22:46:16 +08:00
2021-05-08 22:52:58 +08:00
There are several builtin directive executors, e.g. `form`, `header`, `required`, ... [full list](./directives.go). And you can define your own by:
2021-05-08 22:46:16 +08:00
2021-05-08 22:52:58 +08:00
First, create a **directive executor** by implementing the `httpin.DirectiveExecutor` interface:
2021-05-08 22:46:16 +08:00
```go
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
}
2021-05-08 22:52:58 +08:00
// Adapt toLower to httpin.DirectiveExecutor.
var MyLowercaseDirectiveExecutor = httpin.DirectiveExecutorFunc(toLower)
2021-05-08 22:46:16 +08:00
```
Seconds, register it to `httpin`:
```go
2021-05-08 22:52:58 +08:00
httpin.RegisterDirectiveExecutor("to_lowercase", MyLowercaseDirectiveExecutor)
2021-05-08 22:46:16 +08:00
```
Finally, you can use your own directives in the struct tags:
```go
type Authorization struct {
Token string `in:"form=token;required;to_lowercase"`
}
```
2021-05-08 22:52:58 +08:00
The directives will run in the order as they defined in the struct tag.