From 42c21c179cddadf51ae60fe2ceb17de692555a6f Mon Sep 17 00:00:00 2001 From: Jiun Lee Date: Mon, 16 Oct 2023 22:43:51 -0500 Subject: [PATCH] docs: update binding and validate docs (#819) --- .../basic-feature/binding-and-validate.md | 428 ++++++++++++++++-- .../basic-feature/binding-and-validate.md | 117 +++-- 2 files changed, 455 insertions(+), 90 deletions(-) diff --git a/content/en/docs/hertz/tutorials/basic-feature/binding-and-validate.md b/content/en/docs/hertz/tutorials/basic-feature/binding-and-validate.md index 459c85efe3..71c9941e5f 100644 --- a/content/en/docs/hertz/tutorials/basic-feature/binding-and-validate.md +++ b/content/en/docs/hertz/tutorials/basic-feature/binding-and-validate.md @@ -4,11 +4,8 @@ date: 2022-06-21 weight: 8 keywords: ["Binding and validate", "go-tagexpr", "tag", "Parameter binding precedence"] description: "The parameter binding and validation related functions and usage supported by Hertz." - --- -Hertz uses the open source library [go-tagexpr](https://github.com/bytedance/go-tagexpr) for parameter binding and validation. The following describes the usage of parameter binding and parameter validation. - ## Usage ```go @@ -42,7 +39,24 @@ func main() { } ``` -## Supported tags and parameter binding priorities +### APIs +> +> hertz version >= v0.7.0 + +| API | Description | +|:----------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ctx.BindAndValidate | Use the following go-tag for parameter binding, and do a parameter validation after successful binding (if there is a validation tag) | +| ctx.Bind | Same as `BindAndValidate` but without parameter validation | +| ctx.BindQuery | Bind all Query parameters, which is equivalent to declaring a `query` tag for each field, for scenarios where no tag is written | +| ctx.BindHeader | Bind all Header parameters, which is equivalent to declaring a `header` tag for each field, for scenarios where no tag is written | +| ctx.BindPath | Bind all Path parameters, which is equivalent to declaring a `path` tag for each field, for scenarios where no tag is written | +| ctx.BindForm | Bind all Form parameters, equivalent to declaring a `form` tag for each field, requires Content-Type: `application/x-www-form-urlencoded`/`multipart/form-data`, for scenarios where no tag is written | +| ctx.BindJSON | Bind JSON Body, call `json.Unmarshal()` for deserialization, need Body to be in `application/json` format | +| ctx.BindProtobuf | Bind Protobuf Body, call `proto.Unmarshal()` for deserialization, requires Body to be in `application/x-protobuf` format | +| ctx.BindByContentType | The binding method is automatically selected based on the Content-Type, where GET requests call `BindQuery`, and requests with Body are automatically selected based on the Content-Type. | +| ctx.Validate | Parameter checksums, which require a checksum tag to be used (vd tag checksums are used by default) | + +## Supported tags and Parameter binding precedence ### Supported tags @@ -50,17 +64,17 @@ When generating code without IDL, if no tags are added to the field, it will tra If [api-annotations](/docs/hertz/tutorials/toolkit/annotation/#supported-api-annotations) are not added when generating code through IDL, the fields will default to adding `form`, `JSON`, and `query` tags. Adding [api-annotations](/docs/hertz/tutorials/toolkit/annotation/#supported-api-annotations) will add the corresponding required tags for the fields. -| go tag | description | -| -------- | ------------------------------------------------------------ | -| path | This tag is used to bind parameters on url like `{:param}` or `{*param}`. For example: if we defined route is: `/v:version/example`, you can specify the path parameter as the route parameter: `path:"version"`. In this case if url is http://127.0.0.1:8888/v1/ , you can bind the path parameter "1". | -| form | This tag is used to bind the key-value of the form in request body which content-type is `multipart/form-data` or `application/x-www-form-urlencoded` | -| query | This tag is used to bind query parameter in request | -| cookie | This tag is used to bind cookie parameter in request | -| header | This tag is used to bind header parameters in request | -| json | This tag is used to bind json parameters in the request body which content-type is `application/json` | +| go tag | description | +|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| path | This tag is used to bind parameters on url like `:param` or `*param`. For example: if we defined route is: `/v:version/example`, you can specify the path parameter as the route parameter: `path:"version"`. In this case if url is http://127.0.0.1:8888/v1/ , you can bind the path parameter "1". | +| form | This tag is used to bind the key-value of the form in request body which content-type is `multipart/form-data` or `application/x-www-form-urlencoded` | +| query | This tag is used to bind query parameter in request | +| cookie | This tag is used to bind cookie parameter in request | +| header | This tag is used to bind header parameters in request | +| json | This tag is used to bind json parameters in the request body which content-type is `application/json` | | raw_body | This tag is used to bind the original body (bytes type) of the request, and parameters can be bound even if the bound field name is not specified. (Note: raw_body has the lowest binding priority. When multiple tags are specified, once other tags successfully bind parameters, the body content will not be bound) | -| vd | `vd` short for validator, [The grammar of validation parameter](https://github.com/bytedance/go-tagexpr/tree/master/validator) | -| default | Set default value | +| vd | `vd` short for validator, [The grammar of validation parameter](https://github.com/bytedance/go-tagexpr/tree/master/validator) | +| default | Set default value | ### Parameter binding precedence @@ -84,53 +98,228 @@ type TagRequiredReq struct { } ``` -## Common uses +## Common config +> +> hertz has refactored `parameter binding` and `checksum` in version v0.7.0, which changes the behaviour of the configurations, as described below
respectively. +> If you still want to use the previous binder, it is now implemented under [hertz-contrib/binding](https://github.com/hertz-contrib/binding) and can be introduced via a custom binder. + +### Customise binder +> +> hertz version >= v0.7.0 support + +You need to implement the Binder interface and inject it into the hertz engine in a configurable way. + +```go +type Binder interface { + Name() string // The name of the binder. + // The following are the various binding methods + Bind(*protocol.Request, interface{}, param.Params) error + BindAndValidate(*protocol.Request, interface{}, param.Params) error + BindQuery(*protocol.Request, interface{}) error + BindHeader(*protocol.Request, interface{}) error + BindPath(*protocol.Request, interface{}, param.Params) error + BindForm(*protocol.Request, interface{}) error + BindJSON(*protocol.Request, interface{}) error + BindProtobuf(*protocol.Request, interface{}) error +} +``` + +Example + +```go + +func main() { + // Inject a custom binder via configuration + h := server.New(server.WithCustomBinder(&mockBinder{})) + ... + h.Spin() +} + + +type mockBinder struct{} + +func (m *mockBinder) Name() string { + return "test binder" +} + +func (m *mockBinder) Bind(request *protocol.Request, i interface{}, params param.Params) error { + return nil +} + +func (m *mockBinder) BindAndValidate(request *protocol.Request, i interface{}, params param.Params) error { + return fmt.Errorf("test binder") +} + +func (m *mockBinder) BindQuery(request *protocol.Request, i interface{}) error { + return nil +} + +func (m *mockBinder) BindHeader(request *protocol.Request, i interface{}) error { + return nil +} + +func (m *mockBinder) BindPath(request *protocol.Request, i interface{}, params param.Params) error { + return nil +} + +func (m *mockBinder) BindForm(request *protocol.Request, i interface{}) error { + return nil +} + +func (m *mockBinder) BindJSON(request *protocol.Request, i interface{}) error { + return nil +} + +func (m *mockBinder) BindProtobuf(request *protocol.Request, i interface{}) error { + return nil +} + +``` + +Currently expanded binders: + +* bytedance/go-tagexpr: https://github.com/hertz-contrib/binding/tree/main/go_tagexpr (binding library used before refactoring) + +### Custom validator +> +> Supported by hertz version >= v0.7.0. + +You need to implement the Validator interface and inject it into the hertz engine in a configurable way. + +```go +type StructValidator interface { + ValidateStruct(interface{}) error // Validation function. + Engine() interface{} // Returns the underlying Validator. + ValidateTag() string // Validation tag, declares the tag used by the validator. +} +``` + +Example + +```go + +func main() { + // Inject the custom binder via configuration + h := server.New(server.WithCustomValidator(&mockValidator{})) + ... + h.Spin() +} + +type mockValidator struct{} + +func (m *mockValidator) ValidateStruct(interface{}) error { + return fmt.Errorf("test mock validator") +} + +func (m *mockValidator) Engine() interface{} { + return nil +} + +func (m *mockValidator) ValidateTag() string { + return "vt" +} + +``` + +Currently expanded validators: + +* go-playground/validator: https://github.com/hertz-contrib/binding/tree/main/go_playground ### Customize the error of binding and validation When an error occurs in the binding parameter and the parameter validation fails, user can customize the Error([demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_error))For example: +The user can customise the content of the Error in case of binding parameter errors and parameter validation failures, using the following method:
+**hertz version >= v0.7.0** +> Custom bind errors are not supported at this time. + +Custom validate error: ```go +package main + +import ( + "github.com/cloudwego/hertz/pkg/app/server/binding" + "github.com/cloudwego/hertz/pkg/app/server" +) + +type ValidateError struct { + ErrType, FailField, Msg string +} // Error implements error interface. -func (e *BindError) Error() string { +func (e *ValidateError) Error() string { if e.Msg != "" { return e.ErrType + ": expr_path=" + e.FailField + ", cause=" + e.Msg } return e.ErrType + ": expr_path=" + e.FailField + ", cause=invalid" } +func main() { + validateConfig := &binding.ValidateConfig{} + validateConfig.SetValidatorErrorFactory(func(failField, msg string) error { + err := ValidateError{ + ErrType: "validateErr", + FailField: "[validateFailField]: " + failField, + Msg: "[validateErrMsg]: " + msg, + } + + return &err + }) + h := server.New(server.WithValidateConfig(validateConfig)) + ... + h.Spin() +} +``` + +**hertz version < v0.7.0**
+[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_error) + +```go +import "github.com/cloudwego/hertz/pkg/app/server/binding" + +type BindError struct { + ErrType, FailField, Msg string +} + +// Error implements error interface. +func (e *BindError) Error() string { + if e.Msg != "" { + return e.ErrType + ": expr_path=" + e.FailField + ", cause=" + e.Msg + } + return e.ErrType + ": expr_path=" + e.FailField + ", cause=invalid" +} + type ValidateError struct { - ErrType, FailField, Msg string + ErrType, FailField, Msg string } // Error implements error interface. func (e *ValidateError) Error() string { - if e.Msg != "" { - return e.ErrType + ": expr_path=" + e.FailField + ", cause=" + e.Msg - } - return e.ErrType + ": expr_path=" + e.FailField + ", cause=invalid" + if e.Msg != "" { + return e.ErrType + ": expr_path=" + e.FailField + ", cause=" + e.Msg + } + return e.ErrType + ": expr_path=" + e.FailField + ", cause=invalid" } func init() { CustomBindErrFunc := func(failField, msg string) error { - err := BindError{ - ErrType: "bindErr", - FailField: "[bindFailField]: " + failField, - Msg: "[bindErrMsg]: " + msg, - } - - return &err + err := BindError{ + ErrType: "bindErr", + FailField: "[bindFailField]: " + failField, + Msg: "[bindErrMsg]: " + msg, + } + + return &err } CustomValidateErrFunc := func(failField, msg string) error { - err := ValidateError{ - ErrType: "validateErr", - FailField: "[validateFailField]: " + failField, - Msg: "[validateErrMsg]: " + msg, - } + err := ValidateError{ + ErrType: "validateErr", + FailField: "[validateFailField]: " + failField, + Msg: "[validateErrMsg]: " + msg, + } - return &err + return &err } binding.SetErrorFactory(CustomBindErrFunc, CustomValidateErrFunc) @@ -139,7 +328,8 @@ func init() { ### Customize type resolution -In parameter binding, all request parameters to `string` or `[]string` by default. When some field types are non-basic types or cannot be converted directly through `string`, you can customize type resolution([demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_type_resolve)). For example: +In the parameter binding, for some special types, when the default behavior can not meet the demand, you can use the custom type resolution to solve the problem, the use of the following:
+**hertz version >= v0.7.0**
```go import "github.com/cloudwego/hertz/pkg/app/server/binding" @@ -169,7 +359,112 @@ func init() { ### Customize the validation function -You can implement complex validation logic in the `vd` tag by registering a custom validation function([demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_validate_func)),For example: +```go +package main + +import ( + "github.com/cloudwego/hertz/pkg/app/server/binding" + "github.com/cloudwego/hertz/pkg/app/server" +) + +type Nested struct { + B string + C string +} + +type TestBind struct { + A Nested `query:"a,required"` +} + +func main() { + bindConfig := &binding.BindConfig{} + // After v0.7.0 refactoring, on the basis of the original increase in the request content and routing parameters, + // which can be more flexible for the user to customise the type of parsing + // Note: Only after a tag is successfully matched will the custom logic go through. + bindConfig.MustRegTypeUnmarshal(reflect.TypeOf(Nested{}), func(req *protocol.Request, params param.Params, text string) (reflect.Value, error) { + if text == "" { + return reflect.ValueOf(Nested{}), nil + } + val := Nested{ + B: text[:5], + C: text[5:], + } + // In addition, you can use req, params to get other parameters for parameter binding + return reflect.ValueOf(val), nil + }) + h := server.New(server.WithBindConfig(bindConfig)) + + ... + h.Spin() +} +``` + +**hertz version < v0.7.0**
+ +[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_type_resolve) + +```go +import "github.com/cloudwego/hertz/pkg/app/server/binding" + +type Nested struct { + B string + C string +} + +type TestBind struct { + A Nested `query:"a,required"` +} + +func init() { + binding.MustRegTypeUnmarshal(reflect.TypeOf(Nested{}), func(v string, emptyAsZero bool) (reflect.Value, error) { + if v == "" && emptyAsZero { + return reflect.ValueOf(Nested{}), nil + } + val := Nested{ + B: v[:5], + C: v[5:], + } + return reflect.ValueOf(val), nil + }) +} +``` + +### Custom validation function + +Complex validation logic can be implemented in the `vd` annotation by registering a custom validation function:
+**hertz version >= v0.7.0**
+ +```go +package main + +import ( + "github.com/cloudwego/hertz/pkg/app/server/binding" + "github.com/cloudwego/hertz/pkg/app/server" +) + +func main() { + type Req struct { + A int `query:"a" vd:"test($)"` + } + validateConfig := &binding.ValidateConfig{} + validateConfig.MustRegValidateFunc("test", func(args ...interface{}) error { + if len(args) != 1 { + return fmt.Errorf("the args must be one") + } + s, _ := args[0].(string) + if s == "123" { + return fmt.Errorf("the args can not be 123") + } + return nil + }) + h := server.New(server.WithValidateConfig(validateConfig)) + ... + h.Spin() +} +``` + +**hertz version < v0.7.0**
+[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_validate_func) ```go import "github.com/cloudwego/hertz/pkg/app/server/binding" @@ -188,15 +483,37 @@ func init() { } ``` -### Configure "looseZero" +### Configure looseZero + +In some scenarios, the front-end sometimes passes information that only has a key but not a value, which can lead to errors when binding numeric types; then you need to configure looseZero mode, which can be used as follows: +**hertz version >= v0.7.0**
+ +```go +package main + +import ( + "github.com/cloudwego/hertz/pkg/app/server/binding" + "github.com/cloudwego/hertz/pkg/app/server" +) + +func main() { + bindConfig := binding.NewBindConfig() + // Works for the current Hertz Engine, no conflicts between multiple engine instances bindConfig. + // By default, looseZeroMode is false, and the global configuration is not affected + bindConfig.LooseZeroMode = true + h := server.New(server.WithBindConfig(bindConfig)) + ... + h.Spin() +} +``` -In some cases, the information sent from the front end is **only the key but value empty**, which causes `cause=parameter type does not match binding data` when binding a numeric type. At this time, you need to configure looseZero mode ([demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/loose_zero)). For example: +**hertz version < v0.7.0**
```go import "github.com/cloudwego/hertz/pkg/app/server/binding" func init() { - // Default false, take effect globally + // False by default, globally effective, if other components also use the same configuration, configuration conflicts may occur binding.SetLooseZeroMode(true) } ``` @@ -204,18 +521,37 @@ func init() { ### Configure other json unmarshal libraries When binding parameters, if the request body is json, a json unmarshal will be performed. If users need to use other json libraries (hertz uses the open source json library [sonic](https://github.com/bytedance/sonic) by default), they can configure it themselves. For example: +**hertz version >= v0.7.0**
+ +```go +import ( + "github.com/cloudwego/hertz/pkg/app/server/binding" + "github.com/cloudwego/hertz/pkg/app/server" +) + +func main() { + bindConfig := binding.NewBindConfig() + bindConfig.UseStdJSONUnmarshaler() // use the standard library as the JSON deserialiser, hertz uses sonic as the JSON deserialiser by default + //bindConfig.UseThirdPartyJSONUnmarshaler(sonic.Unmarshal) // Use sonic as the JSON deserialiser. + h := server.New(server.WithBindConfig(bindConfig)) + ... + h.Spin() +} +``` + +**hertz version < v0.7.0**
```go import "github.com/cloudwego/hertz/pkg/app/server/binding" func init() { - // use the standard library + // Use the standard library as a JSON deserialisation tool binding.UseStdJSONUnmarshaler() - - // use gjson + + // Use GJSON as the JSON deserialisation tool. binding.UseGJSONUnmarshaler() - - // use other json unmarshal methods + + // Use third-party JSON libraries as JSON deserialisers. binding.UseThirdPartyJSONUnmarshaler() } ``` @@ -255,10 +591,10 @@ Reason: `string` and `int` conversion is not supported by default Solution: -- We are recommended to use the `string` tag of the standard package json. For example: +* We are recommended to use the `string` tag of the standard package json. For example: ```go A int `json:"A, string"` ``` -- Configure other json libraries that support this operation. +* Configure other json libraries that support this operation. diff --git a/content/zh/docs/hertz/tutorials/basic-feature/binding-and-validate.md b/content/zh/docs/hertz/tutorials/basic-feature/binding-and-validate.md index f75027e832..4fe156e68c 100644 --- a/content/zh/docs/hertz/tutorials/basic-feature/binding-and-validate.md +++ b/content/zh/docs/hertz/tutorials/basic-feature/binding-and-validate.md @@ -4,7 +4,6 @@ date: 2022-05-23 weight: 8 keywords: ["绑定与校验", "go-tagexpr", "tag", "参数绑定优先级"] description: "Hertz 支持的参数绑定与校验相关功能及用法。" - --- ## 使用方法 @@ -39,22 +38,23 @@ func main() { ... } ``` + ### 全部 API +> > hertz version >= v0.7.0 -| API | 说明 | -|:--------------------|:----------------------------------------------------------------------------------------------------------------------------------------| -| ctx.BindAndValidate | 利用下述的 go-tag 进行参数绑定,并在绑定成功后做一次参数校验(如果有校验 tag 的话) | -| ctx.Bind | 同 "BindAndValidate" 但是不做参数校验 | -| ctx.BindQuery | 绑定所有 Query 参数,相当于给每一个 field 声明一个 'query' tag,适用于没写 tag 的场景 | -| ctx.BindHeader | 绑定所有 Header 参数,相当于给每一个 field 声明一个 'header' tag,适用于没写 tag 的场景 | -| ctx.BindPath | 绑定所有 Path 参数,相当于给每一个 field 声明一个 'path' tag,适用于没写 tag 的场景 | -| ctx.BindForm | 绑定所有 Form 参数,相当于给每一个 field 声明一个 'form' tag,需要 Content-Type 为: `application/x-www-form-urlencoded`/`multipart/form-data`, 适用于没写 tag 的场景 | -| ctx.BindJSON | 绑定 JSON Body,调用 json.Unmarshal() 进行反序列化,需要 Body 为 `application/json` 格式 | -| ctx.BindProtobuf | 绑定 Protobuf Body,调用 proto.Unmarshal() 进行反序列化,需要 Body 为 `application/x-protobuf` 格式 | -| ctx.BindByContentType | 根据 Content-Type 来自动选择绑定的方法,其中 "GET" 请求会调用 BindQuery(), 带有 Body 的请求会根据 Content-Type 自动选择 | -| ctx.Validate | 进行参数校验,需要校验 tag 配合使用(默认校验 tag: vd) | - +| API | 说明 | +|:----------------------|:----------------------------------------------------------------------------------------------------------------------------------------| +| ctx.BindAndValidate | 利用下述的 go-tag 进行参数绑定,并在绑定成功后做一次参数校验 (如果有校验 tag 的话) | +| ctx.Bind | 同 `BindAndValidate` 但是不做参数校验 | +| ctx.BindQuery | 绑定所有 Query 参数,相当于给每一个 field 声明一个 `query` tag,适用于没写 tag 的场景 | +| ctx.BindHeader | 绑定所有 Header 参数,相当于给每一个 field 声明一个 `header` tag,适用于没写 tag 的场景 | +| ctx.BindPath | 绑定所有 Path 参数,相当于给每一个 field 声明一个 `path` tag,适用于没写 tag 的场景 | +| ctx.BindForm | 绑定所有 Form 参数,相当于给每一个 field 声明一个 `form` tag,需要 Content-Type 为:`application/x-www-form-urlencoded`/`multipart/form-data`, 适用于没写 tag 的场景 | +| ctx.BindJSON | 绑定 JSON Body,调用 `json.Unmarshal()` 进行反序列化,需要 Body 为 `application/json` 格式 | +| ctx.BindProtobuf | 绑定 Protobuf Body,调用 `proto.Unmarshal()` 进行反序列化,需要 Body 为 `application/x-protobuf` 格式 | +| ctx.BindByContentType | 根据 Content-Type 来自动选择绑定的方法,其中 GET 请求会调用 `BindQuery`, 带有 Body 的请求会根据 Content-Type 自动选择 | +| ctx.Validate | 进行参数校验,需要校验 tag 配合使用 (默认使用 vd tag 校验) | ## 支持的 tag 及参数绑定优先级 @@ -62,19 +62,19 @@ func main() { 不通过 IDL 生成代码时若字段不添加任何 tag 则会遍历所有 tag 并按照优先级绑定参数,添加 tag 则会根据对应的 tag 按照优先级去绑定参数。 -通过 IDL 生成代码时若不添加 [api注解](/zh/docs/hertz/tutorials/toolkit/annotation/#支持的-api-注解) 则字段默认添加 `form`、`json`、`query` tag,添加 [api注解](/zh/docs/hertz/tutorials/toolkit/annotation/#支持的-api-注解) 会为字段添加相应需求的 tag。 +通过 IDL 生成代码时若不添加 [api 注解](/zh/docs/hertz/tutorials/toolkit/annotation/#支持的-api-注解) 则字段默认添加 `form`、`json`、`query` tag,添加 [api 注解](/zh/docs/hertz/tutorials/toolkit/annotation/#支持的-api-注解) 会为字段添加相应需求的 tag。 -| go tag | 说明 | -| :---- | :---- | -| path | 绑定 url 上的路径参数,相当于 hertz 路由{:param}或{*param}中拿到的参数。例如:如果定义的路由为:/v:version/example,可以把 path 的参数指定为路由参数:`path:"version"`,此时,url: http://127.0.0.1:8888/v1/example,可以绑定path参数"1" | -| form | 绑定请求的 body 内容。content-type -> `multipart/form-data` 或 `application/x-www-form-urlencoded`,绑定 form 的 key-value | -| query | 绑定请求的 query 参数 | -| cookie | 绑定请求的 cookie 参数 | -| header | 绑定请求的 header 参数 | -| json | 绑定请求的 body 内容 content-type -> `application/json`,绑定 json 参数 | -| raw_body | 绑定请求的原始 body(bytes),绑定的字段名不指定,也能绑定参数。(注:raw_body 绑定优先级最低,当指定多个 tag 时,一旦其他 tag 成功绑定参数,则不会绑定 body 内容。) | -| vd | 参数校验,[校验语法](https://github.com/bytedance/go-tagexpr/tree/master/validator) | -| default | 设置默认值 | +| go tag | 说明 | +|:---------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| path | 绑定 url 上的路径参数,相当于 hertz 路由 `:param` 或 `*param` 中拿到的参数。例如:如果定义的路由为:/v:version/example,可以把 path 的参数指定为路由参数:`path:"version"`,此时,url: http://127.0.0.1:8888/v1/example,可以绑定path参数"1" | +| form | 绑定请求的 body 内容。content-type -> `multipart/form-data` 或 `application/x-www-form-urlencoded`,绑定 form 的 key-value | +| query | 绑定请求的 query 参数 | +| cookie | 绑定请求的 cookie 参数 | +| header | 绑定请求的 header 参数 | +| json | 绑定请求的 body 内容 content-type -> `application/json`,绑定 json 参数 | +| raw_body | 绑定请求的原始 body(bytes),绑定的字段名不指定,也能绑定参数。(注:raw_body 绑定优先级最低,当指定多个 tag 时,一旦其他 tag 成功绑定参数,则不会绑定 body 内容。) | +| vd | 参数校验,[校验语法](https://github.com/bytedance/go-tagexpr/tree/master/validator) | +| default | 设置默认值 | ### 参数绑定优先级 @@ -82,11 +82,12 @@ func main() { path > form > query > cookie > header > json > raw_body ``` -> 注:如果请求的 content-type 为 `application/json`,`BindAndValidate()`、`Bind()`那么会在参数绑定前做一次 json unmarshal 处理作为兜底。 +> 注:如果请求的 content-type 为 `application/json`,使用 `BindAndValidate`, `Bind`方法会在参数绑定前做一次 json unmarshal 处理。 ### 必传参数 -通过在 tag 中添加 `required`,可以将参数标记为必传。当绑定失败时 `Bind` 和 `BindAndValidate` 将会返回错误。当多个 tag 包含 `required` 时,将会按照优先级绑定。如果所有 tag 都没有绑定上,则会返回错误。 +通过在 tag 中添加 `required`,可以将参数标记为必传。当绑定失败时 `Bind` 和 `BindAndValidate` 将会返回错误。当多个 tag 包含 `required` +时,将会按照优先级绑定。如果**所有** tag 都没有绑定上,则会返回错误。 ``` go type TagRequiredReq struct { @@ -98,12 +99,16 @@ type TagRequiredReq struct { ``` ## 常用配置 -> hertz 在 v0.7.0 版本对参数绑定&校验进行重构,重构后配置的行为发生变更,下面将分别介绍
-> 如果还想使用之前的绑定器,目前已把其实现放到了 hertz-contrib 下,可通过自定义 binder 引入 +> +> hertz 在 v0.7.0 版本对`参数绑定`和`校验`进行了重构,重构后配置的行为发生变更,下面将分别介绍
+> 如果还想使用之前的绑定器,目前已把其实现放到了 [hertz-contrib/binding](https://github.com/hertz-contrib/binding) 下,可通过自定义 binder 引入 +> ### 自定义 binder +> > hertz version >= v0.7.0 支持 需要实现 Binder 接口,并通过配置方式注入到 hertz engine + ```go type Binder interface { Name() string // 绑定器的名字 @@ -118,7 +123,9 @@ type Binder interface { BindProtobuf(*protocol.Request, interface{}) error } ``` -注入 + +注入示例 + ```go func main() { @@ -170,12 +177,15 @@ func (m *mockBinder) BindProtobuf(request *protocol.Request, i interface{}) erro ``` 目前已拓展的绑定器: + * bytedance/go-tagexpr: https://github.com/hertz-contrib/binding/tree/main/go_tagexpr (重构前使用的绑定库) ### 自定义 validator +> > hertz version >= v0.7.0 支持 需要实现 Validator 接口,并通过配置方式注入到 hertz engine + ```go type StructValidator interface { ValidateStruct(interface{}) error // 校验函数 @@ -183,7 +193,9 @@ type StructValidator interface { ValidateTag() string // 校验的 tag, 声明校验器使用的 tag } ``` -注入 + +注入示例 + ```go func main() { @@ -208,16 +220,19 @@ func (m *mockValidator) ValidateTag() string { } ``` + 目前已拓展的校验器: + * go-playground/validator: https://github.com/hertz-contrib/binding/tree/main/go_playground ### 自定义 bind 和 validate 的 Error -绑定参数发生错误和参数校验失败的时候,用户可以自定义 Error 的内容,使用方法如下:
+在绑定参数发生错误和参数校验失败的时候,用户可以自定义 Error 的内容,使用方法如下:
**hertz version >= v0.7.0** > 暂不支持自定义 bind error 自定义 validate error: + ```go package main import ( @@ -255,7 +270,7 @@ func main() { ``` **hertz version < v0.7.0**
-[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_error) +[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_error) ```go import "github.com/cloudwego/hertz/pkg/app/server/binding" @@ -310,8 +325,10 @@ func init() { ``` ### 自定义类型解析 -在参数绑定的时候, 针对某些特殊类型, 默认行为无法满足需求, 可使用自定义类型解析来解决, 使用方法如下:
+ +在参数绑定的时候,针对某些特殊类型,当默认行为无法满足需求时,可使用自定义类型解析来解决,使用方法如下:
**hertz version >= v0.7.0**
+ ```go package main @@ -350,6 +367,7 @@ func main() { h.Spin() } ``` + **hertz version < v0.7.0**
[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_type_resolve) @@ -381,8 +399,10 @@ func init() { ``` ### 自定义验证函数 -可以通过注册自定义验证函数,在'vd'注解中实现复杂的验证逻辑:
+ +可以通过注册自定义验证函数,在 `vd` 注解中实现复杂的验证逻辑:
**hertz version >= v0.7.0**
+ ```go package main @@ -414,6 +434,7 @@ func main() { **hertz version < v0.7.0**
[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_validate_func) + ```go import "github.com/cloudwego/hertz/pkg/app/server/binding" @@ -435,6 +456,7 @@ func init() { 在一些场景下,前端有时候传来的信息只有 key 没有 value,这会导致绑定数值类型的时候报错;这时需要配置 looseZero 模式,使用方法如下: **hertz version >= v0.7.0**
+ ```go package main @@ -445,27 +467,30 @@ import ( func main() { bindConfig := binding.NewBindConfig() - // 默认 false,当前 Hertz Engine 下生效,多份 engine 实例不会冲突 + // 默认 false,当前 Hertz Engine 下生效,多份 engine 实例之间不会冲突 bindConfig.LooseZeroMode = true h := server.New(server.WithBindConfig(bindConfig)) ... h.Spin() } ``` + **hertz version < v0.7.0**
```go import "github.com/cloudwego/hertz/pkg/app/server/binding" func init() { - // 默认 false,全局生效,如何其他组件也使用相关配置,可能会发生冲突 + // 默认 false,全局生效,如果其他组件也使用相关配置,可能会发生配置冲突 binding.SetLooseZeroMode(true) } ``` ### 配置其他 json unmarshal 库 -在绑定参数的时候,如果请求体为 json,会进行一次 json 的 unmarshal,如果用户需要使用特定的 json 库可以自己配置(hertz 默认使用开源 json 库 [sonic](https://github.com/bytedance/sonic) )。使用方法如下:
+ +在绑定参数的时候,如果请求体为 json,会进行一次 json 的 unmarshal,如果用户需要使用特定的 json 库可以自行配置(hertz 默认使用开源 json 库 [sonic](https://github.com/bytedance/sonic) )。使用方法如下:
**hertz version >= v0.7.0**
+ ```go import ( "github.com/cloudwego/hertz/pkg/app/server/binding" @@ -474,14 +499,16 @@ import ( func main() { bindConfig := binding.NewBindConfig() - bindConfig.UseStdJSONUnmarshaler() // 使用标准库作为 JSON 反序列化工具, hertz 默认使用 sonic 作为 JSON 反序列化器 + bindConfig.UseStdJSONUnmarshaler() // 使用标准库作为 JSON 反序列化工具,hertz 默认使用 sonic 作为 JSON 反序列化器 //bindConfig.UseThirdPartyJSONUnmarshaler(sonic.Unmarshal) // 使用 sonic 作为 JSON 反序列化器 h := server.New(server.WithBindConfig(bindConfig)) ... h.Spin() } ``` + **hertz version < v0.7.0**
+ ```go import "github.com/cloudwego/hertz/pkg/app/server/binding" @@ -498,9 +525,10 @@ func init() { ``` ### 设置默认值 +> > 重构前后使用方式都一样 -参数支持 "default" tag 进行默认值的配置,使用方法如下: +参数支持 `default` tag 进行默认值的配置,使用方法如下: ```go // 生成的代码 @@ -510,8 +538,9 @@ type UserInfoResponse struct { ``` ### 绑定文件 +> > 重构前后使用方式一样,IDL 场景不支持文件绑定 -> 文件类型需为: `multipart.FileHeader` +> 文件类型需为:`multipart.FileHeader` 参数绑定支持绑定文件,使用方法如下: @@ -535,10 +564,10 @@ h.POST("/upload", func(ctx context.Context, c *app.RequestContext) { 解决方法: -- 建议使用标准包 json 的 `string` tag, 例如: +* 建议使用标准包 json 的 `string` tag, 例如: ```go A int `json:"A, string"` ``` -- 配置其他支持这种行为的 json 库 +* 配置其他支持这种行为的 json 库