https://github.com/cloudwego/thrift-gen-validator
thrift-gen-validator is a thriftgo plugin to generate struct validators.
https://github.com/cloudwego/thrift-gen-validator
Last synced: about 2 months ago
JSON representation
thrift-gen-validator is a thriftgo plugin to generate struct validators.
- Host: GitHub
- URL: https://github.com/cloudwego/thrift-gen-validator
- Owner: cloudwego
- License: apache-2.0
- Created: 2021-06-28T08:36:16.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2025-02-08T09:50:26.000Z (4 months ago)
- Last Synced: 2025-04-05T12:33:10.553Z (3 months ago)
- Language: Go
- Size: 124 KB
- Stars: 20
- Watchers: 7
- Forks: 9
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# thrift-gen-validator
thrift-gen-validator is a thriftgo plugin to generate struct validators.
Users can define validation rule for struct-like(struct/union/exception) in Thrift file, then the generator will generate `IsValid() error` method for those structs.for example:
```Thrift
enum MapKey {
A, B, C, D, E, F
}struct Example {
1: string Message (vt.min_size = "30") // length of Message should be greater than or equal to 30
2: i32 ID (vt.ge = "10000") // ID must be greater than or euqal to 10000
3: list Values (vt.elem.gt = "0.25") // element of Values must be greater than 0.25
4: map KeyValues (vt.key.defined_only = "true") // value of KeyValues'key must be defined in MapKey
}
```generated method:
```go
func (p *Example) IsValid() error {
if len(p.Message) < int(30) {
return fmt.Errorf("field Message min_len rule failed, current value: %d", len(p.Message))
}
if p.ID < int32(10000) {
return fmt.Errorf("field ID ge rule failed, current value: %v", p.ID)
}
for i := 0; i < len(p.Values); i++ {
_elem := p.Values[i]
if _elem <= float64(0.25) {
return fmt.Errorf("field _elem gt rule failed, current value: %v", _elem)
}
}
for k := range p.KeyValues {
if k.String() == "" {
return fmt.Errorf("field k defined_only rule failed")
}
}
return nil
}
```## Install
`go install github.com/cloudwego/thrift-gen-validator@latest`
## Usage
### Thriftgo
`thriftgo -g go -p validator my.thrift`
### Kitex
`kitex --thrift-plugin validator -service a.b.c my.thrift`
## Feature Matrix
prefix `vt`, short for "validation"
### Numeric(i8/i16/i32/i64/double)
| Rule | |
| --------- | ---------------------------------------- |
| vt.const | must be specified value |
| vt.lt | less than the specified value |
| vt.le | less than or equal to specified value |
| vt.gt | greater than the specified value |
| vt.ge | greater than or equal to specified value |
| vt.in | must be in specified values |
| vt.not_in | must not be in specified values |### Bool
| Rule | |
| -------- | ----------------------- |
| vt.const | must be specified value |### String/Binary
| Rule | |
|------------------|----------------------------------|
| vt.const | must be specified value |
| vt.pattern | regexp pattern |
| vt.prefix | prefix must be specified value |
| vt.suffix | suffix must be specified value |
| vt.contains | must contain specified value |
| vt.not_contains | must not contain specified value |
| vt.min_size | min size |
| vt.max_size | max size |
| vt.max_rune_size | max rune size |
| vt.min_rune_size | min rune size |### Enum
| Rule | |
| --------------- | ----------------------- |
| vt.const | must be specified value |
| vt.defined_only | must be defined value |### Set/List
| Rule | |
| ----------- | --------------------- |
| vt.min_size | min size |
| vt.max_size | max size |
| vt.elem | rule for list element |### Map
| Rule | |
| ------------ | --------------------------------- |
| vt.min_size | min size |
| vt.max_size | max size |
| vt.key | rule for map key |
| vt.value | rule for map value |
| vt.no_sparse | map value must be non-nil pointer |### Struct Field
| Rule | |
| ------- | -------------------------------- |
| vt.skip | skip struct recursive validation |### Struct
| Rule | |
| --------- | ------------------------- |
| vt.assert | expression should be true |### Special Value
1. Field Reference. We can use another field as a validation value.
2. Validation Function. We can use those functions to provide extensive validation ability.#### Field Reference Example
```Thrift
struct Example {
1: string StringFoo (vt.max_size = "$MaxStringSize")
2: i32 MaxStringSize
}
```#### Validation Function
```Thrift
struct Example {
1: string MaxString
2: list StringList (vt.elem.max_size = "@len($MaxString)")
}
```| function name | arguments | results | remarks |
| ------------- | ----------------------------------------------------- | ------------------------------------------------------ | --------------------------------------- |
| len | 1: container field | 1: length of container (integer) | just like `len` of go |
| sprintf | 1: format string
2+: arguments matching format | 1: formatted string (string) | just like `fmt.Sprintf` of go |
| now_unix_nano | none | 1: nano seconds (int64) | just like `time.Now().UnixNano()` of go |
| equal | 1, 2: comparable values | 1: whether two arguments is equal (bool) | just like `==` of go |
| mod | 1, 2: integer | 1: remainder of $1 / $2 (integer) | just like `%` of go |
| add | 1, 2: both are numeric or string | 1: sum of two arguments (integer or float64 or string) | just like `+` of go |#### Customized Validation Function
Now you can use parameter `func` to customize your validation function. Like below:
`thriftgo -g go -p validator:func=my_func=path_to_template.txt my.thrift`
`my_func` is the function name, `path_to_template.txt` is the path to template file which should be a go template.
Available template variables:
| variable name | meaning | type |
| ------------- | ------------------------------------- | ---------------------------------------------------------------- |
| Source | variable name that rule will refer to | string |
| StructLike | ast of current struct/union/exception | *"github.com/cloudwego/thriftgo/generator/golang".StructLike |
| Function | data of current function | *"github.com/cloudwego/thrift-gen-validator/parser".ToolFunction |## Example
### Kitex Middleware Example
```go
package mainimport (
"context"
"fmt"
"log""github.com/cloudwego/kitex-examples/kitex_gen/api"
"github.com/cloudwego/kitex-examples/kitex_gen/api/echo"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/pkg/endpoint"
"github.com/cloudwego/kitex/server"
)func ValidatorMW(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, args, result interface{}) (err error) {
if gfa, ok := args.(interface{ GetFirstArgument() interface{} }); ok {
req := gfa.GetFirstArgument()
if rv, ok := req.(interface{ IsValid() error }); ok {
if err := rv.IsValid(); err != nil {
return fmt.Errorf("request data is not valid:%w", err)
}
}
}
err = next(ctx, args, result)
if err != nil {
return err
}
if gr, ok := result.(interface{ GetResult() interface{} }); ok {
resp := gr.GetResult()
if rv, ok := resp.(interface{ IsValid() error }); ok {
if err := rv.IsValid(); err != nil {
return fmt.Errorf("response data is not valid:%w", err)
}
}
}
return nil
}
}// for client
func main() {
cli := echo.MustNewClient("service_name", client.WithMiddleware(ValidatorMW))
resp, err := client.Echo(context.Background(), &api.Request{Message: "my request"})
if err != nil {
log.Println(err.Error())
} else {
log.Println(resp)
}
}// for server
func main() {
svr := echo.NewServer(new(EchoImpl), server.WithMiddleware(ValidatorMW))
err := svr.Run()
if err != nil {
log.Println(err.Error())
}
}
```### Customize Validation Function Example
If we have a `my.thrift` like below:```Thrift
struct Example {
1: string Message (vt.max_size = "@my_length()")
}
```And assumes that we want to limit the max length of `Message` to 10, we can write a template file `my_length.txt` like below:
```template
{{- .Source}} := 10 /*my length*/
```Then we can use command below to generate a validator file:
`thriftgo -g go -p validator:func=my_length=my_length.txt my.thrift`We will get a `IsValid() error` like below:
```go
func (p *ValidatorExample) IsValid() error {
_src := 10 /*my length*/
if len(p.Message) > int(_src) {
return fmt.Errorf("field Message max_len rule failed, current value: %d", len(p.Message))
}
return nil
}
````{{.Source}}` indicates `_src` which will be used in `if len(p.Message) > int(_src) {`, so all the thing the function template need to do is assign a value to `_src` aka `{{.Source}}`. In the above example, `{{- .Source}} := + 10 /*my length*/` will do.
Now let's see a more complex example. Assumes that we have a `my.thrift` like below:
```Thrift
struct Example {
1: string Message (vt.max_size = "@fix_length($MaxLength)")
2: i64 MaxLength
}
```And assumes that we want to limit the max length of `Message` to the sum of MaxLength and 10, we can write a template file `fix_length.txt` like below:
```template
{{- $arg0 := index .Function.Arguments 0}}
{{- $reference := $arg0.TypedValue.GetFieldReferenceName "p." .StructLike}}
{{- .Source}} := {{$reference}} + 10 /*length fix*/
```Then we can use command below to generate a validator file:
`thriftgo -g go -p validator:func=fix_length=fix_length.txt my.thrift`We will get a `IsValid() error` like below:
```go
func (p *ValidatorExample) IsValid() error {
_src := p.MaxLength + 10 /*length fix*/
if len(p.Message) > int(_src) {
return fmt.Errorf("field Message max_len rule failed, current value: %d", len(p.Message))
}
return nil
}
````{{$arg0 := index .Function.Arguments 0}}` is used to get the first argument of the function. `{{$reference := $arg0.TypedValue.GetFieldReferenceName "p." .StructLike}}` is used to get the reference name of the first argument, for there `p.MaxLength`.
In some scenarios, we might want to import some extra packages, for example, if we want to get some enviroment variables, we need to import `os` package which is not in the default import list. In this case, we can add following statement to function template file:
```template
{{define "Import"}}
"os"
{{end}}
{{define "ImportGuard"}}
_ = os.Getenv
{{end}}
```Then we can get a validator go file header like below:
```go
import (
"bytes"
"fmt"
"os"
"reflect"
"regexp"
"strings"
"time"
)// unused protection
var (
_ = fmt.Formatter(nil)
_ = (*bytes.Buffer)(nil)
_ = (*strings.Builder)(nil)
_ = reflect.Type(nil)
_ = (*regexp.Regexp)(nil)
_ = time.Nanosecond
_ = os.Getenv
)
```You can go to [examples/custom-function](/examples/custom-function/) to see the complete example. And you can view the generated code in [examples/custom-function/gen-go/my](/examples/custom-function/gen-go/my/).