├── .gitignore
├── doc
├── img.png
├── graphql-page.png
└── graphql-run.png
├── example
├── graphql
│ ├── README.md
│ ├── img.png
│ ├── img_1.png
│ ├── sdl
│ │ ├── type.graphql
│ │ └── root.graphql
│ ├── graphql.go
│ ├── graphtype.go
│ └── root.go
├── gin-default
│ └── gin-default.go
├── gin_handler
│ └── gin_handler.go
├── logic_handler
│ └── logic_handler.go
├── default
│ └── cmd.go
├── go.mod
├── openapi.yaml
└── go.sum
├── swagger
├── dist
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── index.css
│ ├── swagger-initializer.js
│ ├── index.html
│ └── oauth2-redirect.html
└── generate.go
├── engine_test.go
├── middleware_test.go
├── logger_test.go
├── logger.go
├── request.go
├── gen_route_test.go
├── schema.go
├── engine.go
├── response.go
├── handle.go
├── handle_test.go
├── ref.go
├── openapi_test.go
├── go.mod
├── util_test.go
├── tracer.go
├── openapi.go
├── new_test.go
├── README.md
├── util.go
├── middleware.go
├── ref_test.go
└── new.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | openapi*.yaml
--------------------------------------------------------------------------------
/doc/img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/doc/img.png
--------------------------------------------------------------------------------
/example/graphql/README.md:
--------------------------------------------------------------------------------
1 | # 运行结果
2 |
3 | 
4 |
5 | 
--------------------------------------------------------------------------------
/doc/graphql-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/doc/graphql-page.png
--------------------------------------------------------------------------------
/doc/graphql-run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/doc/graphql-run.png
--------------------------------------------------------------------------------
/example/graphql/img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/example/graphql/img.png
--------------------------------------------------------------------------------
/example/graphql/img_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/example/graphql/img_1.png
--------------------------------------------------------------------------------
/swagger/dist/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/swagger/dist/favicon-16x16.png
--------------------------------------------------------------------------------
/swagger/dist/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/swagger/dist/favicon-32x32.png
--------------------------------------------------------------------------------
/example/graphql/sdl/type.graphql:
--------------------------------------------------------------------------------
1 | """
2 | This module contains the definition of the UUID scalar.
3 | """
4 | scalar UUID
--------------------------------------------------------------------------------
/swagger/generate.go:
--------------------------------------------------------------------------------
1 | package swagger
2 |
3 | import (
4 | "embed"
5 | _ "embed"
6 | )
7 |
8 | //go:embed dist/*
9 | var Dist embed.FS
10 |
--------------------------------------------------------------------------------
/engine_test.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "testing"
6 | )
7 |
8 | func TestNewCtrlC(t *testing.T) {
9 | ctrlC := NewCtrlC(New(gin.Default()))
10 | ctrlC.Start()
11 | }
12 |
--------------------------------------------------------------------------------
/middleware_test.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestNewMiddleware(t *testing.T) {
8 | NewMiddleware(WithResponse(NewResponse()), WithServerName("gin-plus"), WithID("id"), WithEnv("default"))
9 | }
10 |
--------------------------------------------------------------------------------
/swagger/dist/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | overflow: -moz-scrollbars-vertical;
4 | overflow-y: scroll;
5 | }
6 |
7 | *,
8 | *:before,
9 | *:after {
10 | box-sizing: inherit;
11 | }
12 |
13 | body {
14 | margin: 0;
15 | background: #fafafa;
16 | }
17 |
--------------------------------------------------------------------------------
/logger_test.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "testing"
5 |
6 | "go.uber.org/zap"
7 | )
8 |
9 | func TestLogger(t *testing.T) {
10 | Logger().Info("hello world")
11 | }
12 |
13 | func TestSetLogger(t *testing.T) {
14 | SetLogger(zap.NewExample())
15 | Logger().Info("hello world")
16 | }
17 |
--------------------------------------------------------------------------------
/logger.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "sync"
5 |
6 | "go.uber.org/zap"
7 | )
8 |
9 | var (
10 | logger = zap.NewExample()
11 | loggerOnce = sync.Once{}
12 | )
13 |
14 | // SetLogger 设置日志记录器
15 | func SetLogger(l *zap.Logger) {
16 | loggerOnce.Do(func() {
17 | logger = l
18 | })
19 | }
20 |
21 | // Logger 返回日志记录器
22 | func Logger() *zap.Logger {
23 | return logger
24 | }
25 |
--------------------------------------------------------------------------------
/example/gin-default/gin-default.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | ginplus "github.com/aide-cloud/gin-plus"
5 | "github.com/gin-gonic/gin"
6 | )
7 |
8 | func main() {
9 | instance := ginplus.New(gin.Default())
10 |
11 | instance.GET("/hello", func(ctx *gin.Context) {
12 | ctx.JSON(200, gin.H{
13 | "message": "hello world",
14 | })
15 | })
16 |
17 | ginplus.NewCtrlC(instance).Start()
18 | }
19 |
--------------------------------------------------------------------------------
/example/graphql/graphql.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 |
6 | ginplus "github.com/aide-cloud/gin-plus"
7 | )
8 |
9 | func main() {
10 | instance := ginplus.New(gin.Default(),
11 | ginplus.WithAddr(":8080"),
12 | ginplus.WithGenApiEnable(false),
13 | ginplus.WithGraphqlConfig(ginplus.GraphqlConfig{
14 | Enable: true,
15 | HandlePath: "/graphql",
16 | ViewPath: "/graphql",
17 | Root: &Root{},
18 | Content: content,
19 | }))
20 |
21 | ginplus.NewCtrlC(instance).Start()
22 | }
23 |
--------------------------------------------------------------------------------
/request.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/gin-gonic/gin/binding"
6 | )
7 |
8 | func Bind(c *gin.Context, params interface{}) error {
9 | b := binding.Default(c.Request.Method, c.ContentType())
10 | if err := c.ShouldBindWith(params, b); err != nil {
11 | return err
12 | }
13 |
14 | if err := binding.Form.Bind(c.Request, params); err != nil {
15 | return err
16 | }
17 |
18 | if err := c.ShouldBindUri(params); err != nil {
19 | return err
20 | }
21 |
22 | if err := c.ShouldBindHeader(params); err != nil {
23 | return err
24 | }
25 |
26 | return nil
27 | }
28 |
--------------------------------------------------------------------------------
/swagger/dist/swagger-initializer.js:
--------------------------------------------------------------------------------
1 | window.onload = function() {
2 | //
3 |
4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container
5 | window.ui = SwaggerUIBundle({
6 | url: "/openapi/doc/swagger",
7 | dom_id: '#swagger-ui',
8 | deepLinking: true,
9 | presets: [
10 | SwaggerUIBundle.presets.apis,
11 | SwaggerUIStandalonePreset
12 | ],
13 | plugins: [
14 | SwaggerUIBundle.plugins.DownloadUrl
15 | ],
16 | layout: "StandaloneLayout"
17 | });
18 |
19 | //
20 | };
21 |
--------------------------------------------------------------------------------
/swagger/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Swagger UI
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/example/graphql/graphtype.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/google/uuid"
6 | "github.com/graph-gophers/graphql-go/decode"
7 | )
8 |
9 | type UUID string
10 |
11 | // 自定义类型实现graphql类型接口
12 | var _ decode.Unmarshaler = (*UUID)(nil)
13 | var _ fmt.Stringer = (*UUID)(nil)
14 |
15 | func (*UUID) ImplementsGraphQLType(name string) bool {
16 | return name == "UUID"
17 | }
18 |
19 | func (U *UUID) UnmarshalGraphQL(input interface{}) error {
20 | val, ok := input.(string)
21 | if !ok {
22 | return fmt.Errorf("[UUID] wrong type, input: %T", input)
23 | }
24 |
25 | // 验证是否是uuid
26 | if _, err := uuid.Parse(val); err != nil {
27 | return fmt.Errorf("[UUID] wrong format, input: %s", val)
28 | }
29 |
30 | *U = UUID(val)
31 | return nil
32 | }
33 |
34 | func (U *UUID) String() string {
35 | return string(*U)
36 | }
37 |
--------------------------------------------------------------------------------
/example/graphql/sdl/root.graphql:
--------------------------------------------------------------------------------
1 | schema {
2 | query: RootQuery
3 | mutation: RootMutation
4 | }
5 |
6 | type RootQuery {
7 | ping: String!
8 | extend: ExtendQuery
9 | }
10 |
11 | type ExtendQuery {
12 | info: String!
13 | }
14 |
15 | type RootMutation {
16 | ping: String!
17 | add(in: AddInput!): Int!
18 | sum(in: SumInput!): Int!
19 | sub(in: SubInput!): Int!
20 | mul(in: MulInput!): Int!
21 | div(in: DivInput!): Int!
22 | checkUUID(in: UUID!): String!
23 | }
24 |
25 | input AddInput {
26 | a: Int!
27 | b: Int!
28 | }
29 |
30 | input SumInput {
31 | nums: [Int!]!
32 | }
33 |
34 | input SubInput {
35 | a: Int!
36 | b: Int!
37 | }
38 |
39 | # a * b
40 | input MulInput {
41 | a: Int!
42 | b: Int!
43 | }
44 |
45 | # a / b
46 | input DivInput {
47 | a: Int!
48 | b: Int!
49 | }
--------------------------------------------------------------------------------
/example/gin_handler/gin_handler.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | ginplus "github.com/aide-cloud/gin-plus"
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | var _ ginplus.Controller = (*Api)(nil)
11 | var _ ginplus.Middlewarer = (*Api)(nil)
12 |
13 | type Api struct {
14 | }
15 |
16 | func (a *Api) Middlewares() []gin.HandlerFunc {
17 | return []gin.HandlerFunc{
18 | func(ctx *gin.Context) {
19 | log.Println("middleware 1")
20 | },
21 | }
22 | }
23 |
24 | func (a *Api) BasePath() string {
25 | return "/api/v1"
26 | }
27 |
28 | func (a *Api) GetA() gin.HandlerFunc {
29 | return func(ctx *gin.Context) {
30 | ctx.String(200, "[Get] hello world")
31 | }
32 | }
33 |
34 | func NewApi() *Api {
35 | return &Api{}
36 | }
37 |
38 | func main() {
39 | instance := ginplus.New(gin.Default(), ginplus.WithControllers(NewApi()))
40 |
41 | ginplus.NewCtrlC(instance).Start()
42 | }
43 |
--------------------------------------------------------------------------------
/gen_route_test.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | type Student struct {
11 | A *AApi
12 | B *BApi
13 | }
14 |
15 | type AApi struct {
16 | }
17 |
18 | func (a *AApi) Get(ctx context.Context, req *StudentReq) (*StudentResp, error) {
19 | return &StudentResp{}, nil
20 | }
21 |
22 | type BApi struct {
23 | }
24 |
25 | func (b *BApi) Get(ctx context.Context, req *StudentReq) (*StudentResp, error) {
26 | return &StudentResp{}, nil
27 | }
28 |
29 | type StudentReq struct {
30 | }
31 |
32 | type StudentResp struct {
33 | }
34 |
35 | func (s *Student) Get(ctx context.Context, req *StudentReq) (*StudentResp, error) {
36 | return &StudentResp{}, nil
37 | }
38 |
39 | func TestGenRoute(t *testing.T) {
40 | r := gin.Default()
41 | // var p *Student
42 | p := &Student{
43 | A: &AApi{},
44 | B: &BApi{},
45 | }
46 | pr := New(r, WithControllers(p))
47 | NewCtrlC(pr)
48 | }
49 |
--------------------------------------------------------------------------------
/example/logic_handler/logic_handler.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 |
6 | ginplus "github.com/aide-cloud/gin-plus"
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | type (
11 | Api struct {
12 | V1 *V1
13 | V2 *V2
14 | }
15 |
16 | V1 struct{}
17 |
18 | V2 struct{}
19 |
20 | Req struct {
21 | Id uint `uri:"id"`
22 | Name string `form:"name"`
23 | Data []any `json:"data"`
24 | }
25 | Resp struct {
26 | Id uint `json:"id"`
27 | Name string `json:"name"`
28 | }
29 | )
30 |
31 | func (a *Api) GetInfo(ctx context.Context, req *Req) (*Resp, error) {
32 | return &Resp{
33 | Id: req.Id,
34 | Name: "gin-plus" + req.Name,
35 | }, nil
36 | }
37 |
38 | func (l *V1) PostInfo(ctx context.Context, req *Req) (*Resp, error) {
39 | return &Resp{
40 | Id: req.Id,
41 | Name: "gin-plus" + req.Name,
42 | }, nil
43 | }
44 |
45 | func (l *V2) PutInfo(ctx context.Context, req *Req) (*Resp, error) {
46 | return &Resp{
47 | Id: req.Id,
48 | Name: "gin-plus" + req.Name,
49 | }, nil
50 | }
51 |
52 | func main() {
53 | instance := ginplus.New(gin.Default(), ginplus.WithControllers(&Api{
54 | V1: &V1{},
55 | V2: &V2{},
56 | }))
57 |
58 | // http://localhost:8080/api/info/12?name=xx
59 | ginplus.NewCtrlC(instance).Start()
60 | }
61 |
--------------------------------------------------------------------------------
/schema.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "bytes"
5 | "embed"
6 | "fmt"
7 | "io/fs"
8 | "strings"
9 | )
10 |
11 | // String reads the .graphql schema files from the embed.FS, concatenating the
12 | // files together into one string.
13 | //
14 | // If this method complains about not finding functions AssetNames() or MustAsset(),
15 | // run `go generate` against this package to generate the functions.
16 | func String(content embed.FS) (string, error) {
17 | var buf bytes.Buffer
18 |
19 | fn := func(path string, d fs.DirEntry, err error) error {
20 | if err != nil {
21 | return fmt.Errorf("walking dir: %w", err)
22 | }
23 |
24 | // Only add files with the .graphql extension.
25 | if !strings.HasSuffix(path, ".graphql") {
26 | return nil
27 | }
28 |
29 | b, err := content.ReadFile(path)
30 | if err != nil {
31 | return fmt.Errorf("reading file %q: %w", path, err)
32 | }
33 |
34 | // Add a newline to separate each file.
35 | b = append(b, []byte("\n")...)
36 |
37 | if _, err := buf.Write(b); err != nil {
38 | return fmt.Errorf("writing %q bytes to buffer: %w", path, err)
39 | }
40 |
41 | return nil
42 | }
43 |
44 | // Recursively walk this directory and append all the file contents together.
45 | if err := fs.WalkDir(content, ".", fn); err != nil {
46 | return buf.String(), fmt.Errorf("walking content directory: %w", err)
47 | }
48 |
49 | return buf.String(), nil
50 | }
51 |
--------------------------------------------------------------------------------
/engine.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "os"
5 | "os/signal"
6 | "syscall"
7 | )
8 |
9 | type (
10 | // Starter 开始方法的接口
11 | Starter interface {
12 | Start() error
13 | }
14 |
15 | // Stopper 结束方法的接口
16 | Stopper interface {
17 | Stop()
18 | }
19 |
20 | Server interface {
21 | // Starter 启动服务
22 | Starter
23 | // Stopper 停止服务
24 | Stopper
25 | }
26 |
27 | // CtrlC 捕获ctrl-c的控制器
28 | CtrlC struct {
29 | servers []Server
30 | // 信号通道
31 | signalChan chan os.Signal
32 | }
33 | )
34 |
35 | // NewCtrlC 初始化生成CtrlC
36 | func NewCtrlC(services ...Server) *CtrlC {
37 | return &CtrlC{
38 | servers: services,
39 | signalChan: make(chan os.Signal, 1),
40 | }
41 | }
42 |
43 | // 等待键盘信号
44 | func (c *CtrlC) waitSignals(signals ...os.Signal) {
45 | signal.Notify(c.signalChan, signals...)
46 | <-c.signalChan
47 | }
48 |
49 | // 接收到kill信号
50 | func (c *CtrlC) waitKill() {
51 | c.waitSignals(os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
52 | }
53 |
54 | func (c *CtrlC) recover() {
55 | if err := recover(); err != nil {
56 | Logger().Sugar().Panicf("panic: %v", err)
57 | c.signalChan <- os.Kill
58 | }
59 | }
60 |
61 | // Start 开始运行程序,遇到os.Interrupt停止
62 | func (c *CtrlC) Start() {
63 | go func() {
64 | defer c.recover()
65 | // 启动程序内部的服务列表
66 | c.startMulServices()
67 | }()
68 |
69 | c.waitKill()
70 | c.stopMulServices()
71 | }
72 |
73 | // 启动应用子服务
74 | func (c *CtrlC) startMulServices() {
75 | servicesSlice := c.servers
76 | for _, service := range servicesSlice {
77 | go func(s Starter) {
78 | defer c.recover()
79 | if err := s.Start(); err != nil {
80 | panic(err)
81 | }
82 | }(service)
83 | }
84 | }
85 |
86 | // 停止应用子服务
87 | func (c *CtrlC) stopMulServices() {
88 | servicesSlice := c.servers
89 | for _, service := range servicesSlice {
90 | service.Stop()
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/example/graphql/root.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "embed"
6 | "errors"
7 | )
8 |
9 | //go:embed sdl
10 | var content embed.FS
11 |
12 | type Root struct{}
13 |
14 | func (r *Root) Ping() string {
15 | return "pong"
16 | }
17 |
18 | type (
19 | AddArgs struct {
20 | A int32
21 | B int32
22 | }
23 |
24 | SumArgs struct {
25 | Nums []int32
26 | }
27 |
28 | SubArgs struct {
29 | A int32
30 | B int32
31 | }
32 |
33 | MultiArgs struct {
34 | A int32
35 | B int32
36 | }
37 |
38 | DivArgs struct {
39 | A int32
40 | B int32
41 | }
42 |
43 | Extend struct {
44 | }
45 | )
46 |
47 | func (r *Root) Add(ctx context.Context, args struct {
48 | In AddArgs
49 | }) (int32, error) {
50 | return args.In.A + args.In.B, nil
51 | }
52 |
53 | func (r *Root) Sum(ctx context.Context, args struct {
54 | In SumArgs
55 | }) (int32, error) {
56 | var sum int32
57 | for _, num := range args.In.Nums {
58 | sum += num
59 | }
60 | return sum, nil
61 | }
62 |
63 | func (r *Root) Sub(ctx context.Context, args struct {
64 | In SubArgs
65 | }) (int32, error) {
66 | return args.In.A - args.In.B, nil
67 | }
68 |
69 | func (r *Root) Mul(ctx context.Context, args struct {
70 | In MultiArgs
71 | }) (int32, error) {
72 | return args.In.A * args.In.B, nil
73 | }
74 |
75 | func (r *Root) Div(ctx context.Context, args struct {
76 | In DivArgs
77 | }) (int32, error) {
78 | if args.In.B == 0 {
79 | return 0, errors.New("div by zero")
80 | }
81 | return args.In.A / args.In.B, nil
82 | }
83 |
84 | func (r *Root) CheckUUID(ctx context.Context, args struct {
85 | In UUID
86 | }) (string, error) {
87 | return args.In.String(), nil
88 | }
89 |
90 | func (r *Root) Extend(ctx context.Context) *Extend {
91 | return &Extend{}
92 | }
93 |
94 | // Info ...
95 | // request:
96 | //
97 | // {
98 | // extend {
99 | // info
100 | // }
101 | // }
102 | //
103 | // response:
104 | //
105 | // {
106 | // "data": {
107 | // "extend": {
108 | // "info": "extend info"
109 | // }
110 | // }
111 | // }
112 | func (r *Extend) Info(ctx context.Context) string {
113 | return "extend info"
114 | }
115 |
--------------------------------------------------------------------------------
/response.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "errors"
5 | "reflect"
6 |
7 | "github.com/gin-gonic/gin"
8 | "go.uber.org/zap"
9 | )
10 |
11 | type IResponse interface {
12 | Response(ctx *gin.Context, resp any, err error)
13 | }
14 |
15 | type IValidator interface {
16 | Validate() error
17 | }
18 |
19 | type response struct {
20 | Error error `json:"error"`
21 | Data any `json:"data"`
22 | }
23 |
24 | var _ IResponse = (*response)(nil)
25 |
26 | func NewResponse() IResponse {
27 | return &response{}
28 | }
29 |
30 | func (l *response) Response(ctx *gin.Context, resp any, err error) {
31 | defer ctx.Abort()
32 | ctx.JSON(200, &response{
33 | Error: err,
34 | Data: resp,
35 | })
36 | }
37 |
38 | func (l *GinEngine) newDefaultHandler(controller any, t reflect.Method, req reflect.Type) gin.HandlerFunc {
39 | // 缓存反射数据, 避免在请求中再处理导致性能问题
40 | reqTmp := req
41 | for reqTmp.Kind() == reflect.Ptr {
42 | reqTmp = reqTmp.Elem()
43 | }
44 |
45 | handleFunc := t.Func
46 | controllerVal := reflect.ValueOf(controller)
47 | return func(ctx *gin.Context) {
48 | // new一个req的实例
49 | reqVal := reflect.New(reqTmp)
50 | // 绑定请求参数
51 | if err := l.defaultBind(ctx, reqVal.Interface()); err != nil {
52 | Logger().Info("defaultBind req err", zap.Error(err))
53 | l.defaultResponse.Response(ctx, nil, err)
54 | return
55 | }
56 |
57 | // Validate
58 | if validate, ok := reqVal.Interface().(IValidator); ok {
59 | if err := validate.Validate(); err != nil {
60 | Logger().Info("Validate req err", zap.Error(err))
61 | l.defaultResponse.Response(ctx, nil, err)
62 | return
63 | }
64 | }
65 |
66 | // 调用方法
67 | respVal := handleFunc.Call([]reflect.Value{controllerVal, reflect.ValueOf(ctx), reqVal})
68 | if !respVal[1].IsNil() {
69 | err, ok := respVal[1].Interface().(error)
70 | if ok {
71 | Logger().Info("handleFunc Call err", zap.Error(err))
72 | l.defaultResponse.Response(ctx, nil, err)
73 | return
74 | }
75 | Logger().Info("handleFunc Call abnormal err", zap.Error(err))
76 | l.defaultResponse.Response(ctx, nil, errors.New("response error"))
77 | return
78 | }
79 |
80 | // 返回结果
81 | l.defaultResponse.Response(ctx, respVal[0].Interface(), nil)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/handle.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "embed"
5 | "fmt"
6 | "net/http"
7 |
8 | "github.com/graph-gophers/graphql-go"
9 | "github.com/graph-gophers/graphql-go/relay"
10 | )
11 |
12 | const (
13 | DefaultHandlePath = "/graphql"
14 | DefaultViewPath = "/view"
15 | )
16 |
17 | func View(handlePath string) http.HandlerFunc {
18 | var page = []byte(`
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Loading...
30 |
52 |
53 |
54 | `)
55 |
56 | return func(w http.ResponseWriter, r *http.Request) {
57 | _, err := w.Write(page)
58 | if err != nil {
59 | panic(fmt.Sprintf("write page error: %s\n", err))
60 | }
61 | }
62 | }
63 |
64 | func Handler(root any, content embed.FS) *relay.Handler {
65 | s, err := String(content)
66 | if err != nil {
67 | panic(fmt.Sprintf("reading embedded schema contents: %v", err))
68 | }
69 |
70 | return &relay.Handler{Schema: graphql.MustParseSchema(s, root, graphql.UseFieldResolvers())}
71 | }
72 |
--------------------------------------------------------------------------------
/example/default/cmd.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 |
7 | ginplush "github.com/aide-cloud/gin-plus"
8 | "github.com/gin-gonic/gin"
9 | )
10 |
11 | type (
12 | Api struct {
13 | }
14 |
15 | ApiDetailReq struct {
16 | Id uint `uri:"id"`
17 | }
18 |
19 | ApiDetailResp struct {
20 | Id uint `json:"id"`
21 | Name string `json:"name"`
22 | Remark string `json:"remark"`
23 | }
24 |
25 | ApiListReq struct {
26 | Current int `form:"current"`
27 | Size int `form:"size"`
28 | Keryworld string `form:"keyworld"`
29 | }
30 | ApiListResp struct {
31 | Total int64 `json:"total"`
32 | List []*ApiInfoItem `json:"list"`
33 | }
34 |
35 | ApiInfoItem struct {
36 | Name string `json:"name"`
37 | Id uint `json:"id"`
38 | Remark string `json:"remark"`
39 | }
40 |
41 | ApiUpdateReq struct {
42 | Id uint `uri:"id"`
43 | Name string `json:"name"`
44 | Remark string `json:"remark"`
45 | }
46 | ApiUpdateResp struct {
47 | Id uint `json:"id"`
48 | }
49 |
50 | DelApiReq struct {
51 | Id uint `uri:"id"`
52 | }
53 |
54 | DelApiResp struct {
55 | Id uint `json:"id"`
56 | }
57 | )
58 |
59 | func (l *Api) GetDetail(ctx context.Context, req *ApiDetailReq) (*ApiDetailResp, error) {
60 | log.Println("Api.GetDetail")
61 | return &ApiDetailResp{
62 | Id: req.Id,
63 | Name: "demo",
64 | Remark: "hello world",
65 | }, nil
66 | }
67 |
68 | func (l *Api) GetList(ctx context.Context, req *ApiListReq) (*ApiListResp, error) {
69 | log.Println("Api.GetList", req)
70 | return &ApiListResp{
71 | Total: 100,
72 | List: []*ApiInfoItem{
73 | {
74 | Id: 10,
75 | Name: "demo",
76 | Remark: "hello world",
77 | },
78 | },
79 | }, nil
80 | }
81 |
82 | func (l *Api) UpdateInfo(ctx context.Context, req *ApiUpdateReq) (*ApiUpdateResp, error) {
83 | log.Println("Api.UpdateInfo")
84 | return &ApiUpdateResp{Id: req.Id}, nil
85 | }
86 |
87 | func (l *Api) DeleteInfo(ctx context.Context, req *DelApiReq) (*DelApiResp, error) {
88 | log.Println("Api.DeleteInfo")
89 | return &DelApiResp{Id: req.Id}, nil
90 | }
91 |
92 | func main() {
93 | r := gin.Default()
94 | ginInstance := ginplush.New(r, ginplush.WithControllers(&Api{}))
95 | ginInstance.Run(":8080")
96 | }
97 |
--------------------------------------------------------------------------------
/handle_test.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/gin-gonic/gin"
9 | oteltrace "go.opentelemetry.io/otel/trace"
10 | "gorm.io/driver/mysql"
11 | "gorm.io/gorm"
12 | )
13 |
14 | type HandleApi struct {
15 | }
16 |
17 | func (l *HandleApi) Get() gin.HandlerFunc {
18 | sqlDB, err := sql.Open("mysql", "root:12345678@tcp(localhost:3060)/prom?charset=utf8mb4&parseTime=True&loc=Local")
19 | if err != nil {
20 | panic(err)
21 | }
22 | db, err := gorm.Open(mysql.New(mysql.Config{
23 | Conn: sqlDB,
24 | }))
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | db = db.Debug()
30 |
31 | if err := db.Use(&OpentracingPlugin{}); err != nil {
32 | panic(err)
33 | }
34 | return func(c *gin.Context) {
35 | _span := c.Value("span")
36 | fmt.Printf("span: %T\n", _span)
37 | ctx := c.Request.Context()
38 | span, ok := _span.(oteltrace.Span)
39 | if ok {
40 | fmt.Println("--------------ok------------------")
41 | ctx, span = span.TracerProvider().Tracer("api").Start(ctx, "HandleApi.Get")
42 | defer span.End()
43 | }
44 | type Dict struct {
45 | Id uint `gorm:"column:id;primaryKey;autoIncrement;comment:主键ID"`
46 | Name string `gorm:"column:name;type:varchar(255);not null;comment:字典名称"`
47 | }
48 | list := make([]*Dict, 0)
49 | err = db.Table("prom_dict").WithContext(ctx).Find(&list).Error
50 | if err != nil {
51 | c.String(200, err.Error())
52 | return
53 | }
54 |
55 | var first Dict
56 | if err := db.Table("prom_dict").WithContext(ctx).First(&first).Error; err != nil {
57 | c.String(200, err.Error())
58 | return
59 | }
60 |
61 | if err := db.Table("prom_dict").WithContext(ctx).Where("id = 0").Updates(&first).Error; err != nil {
62 | c.String(200, err.Error())
63 | return
64 | }
65 |
66 | if err := db.Table("prom_dict").WithContext(ctx).Where("id = 0").Delete(&first).Error; err != nil {
67 | c.String(200, err.Error())
68 | return
69 | }
70 |
71 | c.JSON(200, list)
72 | }
73 | }
74 |
75 | func NewHandleApiApi() *HandleApi {
76 | return &HandleApi{}
77 | }
78 |
79 | func TestApiHandleFunc(t *testing.T) {
80 | ginR := gin.New()
81 |
82 | midd := NewMiddleware()
83 |
84 | ginR.Use(
85 | midd.Tracing("http://localhost:14268/api/traces"),
86 | midd.Logger(),
87 | midd.IpLimit(100, 5),
88 | midd.Interceptor(),
89 | midd.Cors(),
90 | )
91 |
92 | r := New(ginR, WithControllers(NewHandleApiApi()))
93 |
94 | NewCtrlC(r).Start()
95 | }
96 |
--------------------------------------------------------------------------------
/ref.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "reflect"
5 | "strings"
6 | )
7 |
8 | type (
9 | Field struct {
10 | Name string
11 | Info []FieldInfo
12 | }
13 |
14 | FieldInfo struct {
15 | Type string
16 | Name string
17 | Tags Tag
18 | ChildType string
19 | Info []FieldInfo
20 | }
21 |
22 | Tag struct {
23 | FormKey string
24 | UriKey string
25 | Skip string
26 | JsonKey string
27 | Title string
28 | Format string
29 | Desc string
30 | }
31 | )
32 |
33 | var tags = []string{"form", "uri", "json", "title", "format", "desc", "skip"}
34 |
35 | // var 临时存储已经处理过的结构体
36 | var tmpStruct = make(map[string]struct{})
37 |
38 | // 获取结构体tag
39 | func getTag(t reflect.Type) []FieldInfo {
40 | if _, ok := tmpStruct[t.String()]; ok {
41 | return nil
42 | }
43 | tmpStruct[t.String()] = struct{}{}
44 | tmp := t
45 | for tmp.Kind() == reflect.Ptr {
46 | tmp = tmp.Elem()
47 | }
48 |
49 | if tmp.Kind() == reflect.Slice {
50 | tmp = tmp.Elem()
51 | for tmp.Kind() == reflect.Ptr {
52 | tmp = tmp.Elem()
53 | }
54 | }
55 |
56 | if tmp.Kind() != reflect.Struct {
57 | return nil
58 | }
59 |
60 | fieldList := make([]FieldInfo, 0, tmp.NumField())
61 | for i := 0; i < tmp.NumField(); i++ {
62 | field := tmp.Field(i)
63 | fieldName := field.Name
64 | fieldType := field.Type.String()
65 | tagInfo := Tag{
66 | Title: fieldName,
67 | }
68 | for _, tagKey := range tags {
69 | tagVal, ok := field.Tag.Lookup(tagKey)
70 | if !ok {
71 | continue
72 | }
73 |
74 | switch tagKey {
75 | case "form":
76 | tagInfo.FormKey = tagVal
77 | case "uri":
78 | tagInfo.UriKey = tagVal
79 | case "skip":
80 | tagInfo.Skip = tagVal
81 | case "title":
82 | tagInfo.Title = tagVal
83 | case "format":
84 | tagInfo.Format = tagVal
85 | case "desc":
86 | tagInfo.Desc = tagVal
87 | default:
88 | valList := strings.Split(tagVal, ",")
89 | tagInfo.JsonKey = valList[0]
90 | }
91 | }
92 |
93 | childType := field.Type
94 | if childType.Kind() == reflect.Slice {
95 | childType = childType.Elem()
96 | for childType.Kind() == reflect.Ptr {
97 | childType = childType.Elem()
98 | }
99 | }
100 |
101 | fieldList = append(fieldList, FieldInfo{
102 | Type: fieldType,
103 | Name: fieldName,
104 | Tags: tagInfo,
105 | ChildType: childType.String(),
106 | Info: getTag(childType),
107 | })
108 | }
109 |
110 | return fieldList
111 | }
112 |
--------------------------------------------------------------------------------
/example/go.mod:
--------------------------------------------------------------------------------
1 | module gin-plus-demo
2 |
3 | go 1.21.0
4 |
5 | require (
6 | github.com/aide-cloud/gin-plus v0.1.7
7 | github.com/gin-gonic/gin v1.9.1
8 | github.com/google/uuid v1.1.2
9 | github.com/graph-gophers/graphql-go v1.5.0
10 | )
11 |
12 | require (
13 | github.com/beorn7/perks v1.0.1 // indirect
14 | github.com/bytedance/sonic v1.10.0 // indirect
15 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
16 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
17 | github.com/chenzhuoyu/iasm v0.9.0 // indirect
18 | github.com/fsnotify/fsnotify v1.6.0 // indirect
19 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
20 | github.com/gin-contrib/sse v0.1.0 // indirect
21 | github.com/go-playground/locales v0.14.1 // indirect
22 | github.com/go-playground/universal-translator v0.18.1 // indirect
23 | github.com/go-playground/validator/v10 v10.15.3 // indirect
24 | github.com/goccy/go-json v0.10.2 // indirect
25 | github.com/golang/protobuf v1.5.3 // indirect
26 | github.com/hashicorp/hcl v1.0.0 // indirect
27 | github.com/json-iterator/go v1.1.12 // indirect
28 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect
29 | github.com/leodido/go-urn v1.2.4 // indirect
30 | github.com/magiconair/properties v1.8.7 // indirect
31 | github.com/mattn/go-isatty v0.0.19 // indirect
32 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
33 | github.com/mitchellh/mapstructure v1.5.0 // indirect
34 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
35 | github.com/modern-go/reflect2 v1.0.2 // indirect
36 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect
37 | github.com/prometheus/client_golang v1.16.0 // indirect
38 | github.com/prometheus/client_model v0.4.0 // indirect
39 | github.com/prometheus/common v0.44.0 // indirect
40 | github.com/prometheus/procfs v0.11.1 // indirect
41 | github.com/spf13/afero v1.9.5 // indirect
42 | github.com/spf13/cast v1.5.1 // indirect
43 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
44 | github.com/spf13/pflag v1.0.5 // indirect
45 | github.com/spf13/viper v1.16.0 // indirect
46 | github.com/subosito/gotenv v1.4.2 // indirect
47 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
48 | github.com/ugorji/go/codec v1.2.11 // indirect
49 | golang.org/x/arch v0.4.0 // indirect
50 | golang.org/x/crypto v0.12.0 // indirect
51 | golang.org/x/net v0.14.0 // indirect
52 | golang.org/x/sys v0.12.0 // indirect
53 | golang.org/x/text v0.12.0 // indirect
54 | google.golang.org/protobuf v1.31.0 // indirect
55 | gopkg.in/ini.v1 v1.67.0 // indirect
56 | gopkg.in/yaml.v3 v3.0.1 // indirect
57 | )
58 |
--------------------------------------------------------------------------------
/openapi_test.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "context"
5 | "log"
6 | "testing"
7 |
8 | "github.com/gin-gonic/gin"
9 | )
10 |
11 | type (
12 | Api struct {
13 | }
14 |
15 | ApiDetailReq struct {
16 | Id uint `uri:"id" desc:"数据自增唯一ID"`
17 | }
18 |
19 | ApiDetailResp struct {
20 | Id uint `json:"id"`
21 | Name string `json:"name"`
22 | Remark string `json:"remark"`
23 | }
24 |
25 | ApiListReq struct {
26 | Current int `form:"current"`
27 | Size int `form:"size"`
28 | Keyword string `form:"keyword"`
29 | }
30 | ApiListResp struct {
31 | Total int64 `json:"total"`
32 | List []*ApiInfoItem `json:"list"`
33 | }
34 |
35 | Extent struct {
36 | A `json:"a"`
37 | B `json:"b"`
38 | }
39 |
40 | ApiInfoItem struct {
41 | Name string `json:"name"`
42 | Id uint `json:"id"`
43 | Remark string `json:"remark"`
44 | Extent Extent `json:"extent"`
45 | }
46 |
47 | ApiUpdateReq struct {
48 | Id uint `uri:"id"`
49 | Name string `json:"name"`
50 | Remark string `json:"remark"`
51 | }
52 | ApiUpdateResp struct {
53 | Id uint `json:"id"`
54 | }
55 |
56 | DelApiReq struct {
57 | Id uint `uri:"id"`
58 | }
59 |
60 | DelApiResp struct {
61 | Id uint `json:"id"`
62 | }
63 | )
64 |
65 | func (l *Api) GetDetail(_ context.Context, req *ApiDetailReq) (*ApiDetailResp, error) {
66 | log.Println("Api.GetDetail")
67 | return &ApiDetailResp{
68 | Id: req.Id,
69 | Name: "demo",
70 | Remark: "hello world",
71 | }, nil
72 | }
73 |
74 | func (l *Api) GetList(_ context.Context, _ *ApiListReq) (*ApiListResp, error) {
75 | log.Println("Api.GetList")
76 | return &ApiListResp{
77 | Total: 100,
78 | List: []*ApiInfoItem{
79 | {
80 | Id: 10,
81 | Name: "demo",
82 | Remark: "hello world",
83 | },
84 | },
85 | }, nil
86 | }
87 |
88 | func (l *Api) UpdateInfo(_ context.Context, req *ApiUpdateReq) (*ApiUpdateResp, error) {
89 | log.Println("Api.UpdateInfo")
90 | return &ApiUpdateResp{Id: req.Id}, nil
91 | }
92 |
93 | func (l *Api) DeleteInfo(_ context.Context, req *DelApiReq) (*DelApiResp, error) {
94 | log.Println("Api.DeleteInfo")
95 | return &DelApiResp{Id: req.Id}, nil
96 | }
97 |
98 | func TestGinEngine_execute(t *testing.T) {
99 | New(gin.Default(),
100 | WithControllers(&Api{}),
101 | WithOpenApiYaml("./", "openapi.yaml"),
102 | AppendHttpMethodPrefixes(
103 | HttpMethod{
104 | Prefix: "Update",
105 | Method: Put,
106 | }, HttpMethod{
107 | Prefix: "Del",
108 | Method: Delete,
109 | },
110 | ),
111 | )
112 | }
113 |
--------------------------------------------------------------------------------
/swagger/dist/oauth2-redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Swagger UI: OAuth2 Redirect
5 |
6 |
7 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/aide-cloud/gin-plus
2 |
3 | go 1.21
4 |
5 | require (
6 | github.com/gin-gonic/gin v1.9.1
7 | github.com/graph-gophers/graphql-go v1.5.0
8 | github.com/prometheus/client_golang v1.16.0
9 | github.com/spf13/viper v1.16.0
10 | go.opentelemetry.io/otel v1.18.0
11 | go.opentelemetry.io/otel/exporters/jaeger v1.17.0
12 | go.opentelemetry.io/otel/sdk v1.18.0
13 | go.opentelemetry.io/otel/trace v1.18.0
14 | go.uber.org/zap v1.21.0
15 | gorm.io/driver/mysql v1.5.1
16 | gorm.io/gorm v1.25.4
17 | )
18 |
19 | require (
20 | github.com/beorn7/perks v1.0.1 // indirect
21 | github.com/bytedance/sonic v1.9.1 // indirect
22 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
23 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
24 | github.com/fsnotify/fsnotify v1.6.0 // indirect
25 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
26 | github.com/gin-contrib/sse v0.1.0 // indirect
27 | github.com/go-logr/logr v1.2.4 // indirect
28 | github.com/go-logr/stdr v1.2.2 // indirect
29 | github.com/go-playground/locales v0.14.1 // indirect
30 | github.com/go-playground/universal-translator v0.18.1 // indirect
31 | github.com/go-playground/validator/v10 v10.14.0 // indirect
32 | github.com/go-sql-driver/mysql v1.7.0 // indirect
33 | github.com/goccy/go-json v0.10.2 // indirect
34 | github.com/golang/protobuf v1.5.3 // indirect
35 | github.com/hashicorp/hcl v1.0.0 // indirect
36 | github.com/jinzhu/inflection v1.0.0 // indirect
37 | github.com/jinzhu/now v1.1.5 // indirect
38 | github.com/json-iterator/go v1.1.12 // indirect
39 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
40 | github.com/leodido/go-urn v1.2.4 // indirect
41 | github.com/magiconair/properties v1.8.7 // indirect
42 | github.com/mattn/go-isatty v0.0.19 // indirect
43 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
44 | github.com/mitchellh/mapstructure v1.5.0 // indirect
45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
46 | github.com/modern-go/reflect2 v1.0.2 // indirect
47 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
48 | github.com/prometheus/client_model v0.4.0 // indirect
49 | github.com/prometheus/common v0.44.0 // indirect
50 | github.com/prometheus/procfs v0.11.1 // indirect
51 | github.com/spf13/afero v1.9.5 // indirect
52 | github.com/spf13/cast v1.5.1 // indirect
53 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
54 | github.com/spf13/pflag v1.0.5 // indirect
55 | github.com/subosito/gotenv v1.4.2 // indirect
56 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
57 | github.com/ugorji/go/codec v1.2.11 // indirect
58 | go.opentelemetry.io/otel/metric v1.18.0 // indirect
59 | go.uber.org/atomic v1.11.0 // indirect
60 | go.uber.org/multierr v1.8.0 // indirect
61 | golang.org/x/arch v0.3.0 // indirect
62 | golang.org/x/crypto v0.9.0 // indirect
63 | golang.org/x/net v0.10.0 // indirect
64 | golang.org/x/sys v0.12.0 // indirect
65 | golang.org/x/text v0.9.0 // indirect
66 | google.golang.org/protobuf v1.31.0 // indirect
67 | gopkg.in/ini.v1 v1.67.0 // indirect
68 | gopkg.in/yaml.v3 v3.0.1 // indirect
69 | )
70 |
--------------------------------------------------------------------------------
/util_test.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "context"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/gin-gonic/gin"
9 | )
10 |
11 | type A struct {
12 | }
13 |
14 | type B struct {
15 | }
16 |
17 | func (a *A) Middlewares() []gin.HandlerFunc {
18 | return nil
19 | }
20 |
21 | func Test_isController(t *testing.T) {
22 | type args struct {
23 | c any
24 | }
25 |
26 | tests := []struct {
27 | name string
28 | args args
29 | want bool
30 | }{
31 | {
32 | name: "test",
33 | args: args{
34 | c: &A{},
35 | },
36 | want: true,
37 | },
38 | {
39 | name: "test",
40 | args: args{
41 | c: &B{},
42 | },
43 | want: false,
44 | },
45 | }
46 | for _, tt := range tests {
47 | t.Run(tt.name, func(t *testing.T) {
48 | _, got := isMiddleware(tt.args.c)
49 | if got != tt.want {
50 | t.Errorf("isController() = %v, want %v", got, tt.want)
51 | }
52 | })
53 | }
54 | }
55 |
56 | type (
57 | Call struct {
58 | }
59 | Req struct {
60 | Name string `json:"name"`
61 | }
62 | Resp struct {
63 | Code int `json:"code"`
64 | Msg string `json:"msg"`
65 | Data any `json:"data"`
66 | }
67 | )
68 |
69 | func (c *Call) CallBack(_ context.Context, _ Req) (Resp, error) {
70 | return Resp{}, nil
71 | }
72 |
73 | func Test_isCallBack(t *testing.T) {
74 | ty := reflect.TypeOf(&Call{})
75 | m, ok := ty.MethodByName("CallBack")
76 | if !ok {
77 | t.Error("not found method")
78 | }
79 |
80 | req, resp, ok := isCallBack(m.Type)
81 | if ok {
82 | t.Error("not found method CallBack: ", m.Type.String())
83 | }
84 |
85 | t.Log(req, resp)
86 | }
87 |
88 | func TestGinEngine_parseRoute(t *testing.T) {
89 | instance := New(gin.Default())
90 | t.Log(instance.parseRoute("Delete"))
91 | }
92 |
93 | func TestGinEngine_isPublic(t *testing.T) {
94 | if !isPublic("Abc") {
95 | t.Log("Abc is public")
96 | }
97 |
98 | if isPublic("abc") {
99 | t.Log("abc is private")
100 | }
101 | }
102 |
103 | type Pub struct {
104 | v1 *V1
105 | V1x *V1
106 | *V1
107 | }
108 |
109 | type V1 struct {
110 | }
111 |
112 | type PubReq struct {
113 | EID string `uri:"eid"`
114 | Id int `uri:"id"`
115 | Name string `json:"name"`
116 | }
117 |
118 | type PubResp struct {
119 | Code int `json:"code"`
120 | Msg string `json:"msg"`
121 | Data any `json:"data"`
122 | }
123 |
124 | func (p *Pub) GetPingA(_ context.Context, _ *PubReq) (*PubResp, error) {
125 | return nil, nil
126 | }
127 |
128 | func (p *Pub) getPingB(_ context.Context, _ *PubReq) (*PubResp, error) {
129 | return nil, nil
130 | }
131 |
132 | func (p *V1) GetList(_ context.Context, _ *PubReq) (*PubResp, error) {
133 | return nil, nil
134 | }
135 |
136 | func TestPrivateMethod(t *testing.T) {
137 | New(gin.Default(), WithControllers(&Pub{
138 | v1: &V1{},
139 | }), AppendHttpMethodPrefixes(
140 | HttpMethod{
141 | Prefix: "get",
142 | Method: httpMethod{get},
143 | },
144 | ))
145 | }
146 |
147 | func TestGinEngine_genStructRoute(t *testing.T) {
148 | New(gin.Default(), WithControllers(&Pub{
149 | v1: &V1{},
150 | }), AppendHttpMethodPrefixes(
151 | HttpMethod{
152 | Prefix: "Get",
153 | Method: httpMethod{get},
154 | },
155 | ))
156 | }
157 |
158 | type GenRouteV1 struct{}
159 | type GenRouteV2 struct{}
160 | type GenRouteApi struct {
161 | ApiV1 *GenRouteV1
162 | ApiV2 *GenRouteV2
163 | }
164 |
165 | func (g *GenRouteV1) GetPing(_ context.Context, _ *PubReq) (*PubResp, error) {
166 | return nil, nil
167 | }
168 | func (g *GenRouteV2) PostPing(_ context.Context, _ *PubReq) (*PubResp, error) {
169 | return nil, nil
170 | }
171 | func (g *GenRouteApi) PutPing(_ context.Context, _ *PubReq) (*PubResp, error) {
172 | return nil, nil
173 | }
174 | func (g *GenRouteApi) DeletePing(_ context.Context, _ *PubReq) (*PubResp, error) {
175 | return nil, nil
176 | }
177 |
178 | func TestGinEngine_genRoute(t *testing.T) {
179 | New(gin.Default(), WithControllers(&GenRouteApi{
180 | ApiV1: &GenRouteV1{},
181 | ApiV2: &GenRouteV2{},
182 | }))
183 | }
184 |
185 | type LogicApi struct{}
186 |
187 | type LogicApiReq struct {
188 | EID string `uri:"eid" skip:"true"`
189 | PID uint `uri:"pid" skip:"true"`
190 | Id int `uri:"id"`
191 | }
192 | type LogicApiResp struct {
193 | Code int `json:"code"`
194 | Msg string `json:"msg"`
195 | Data any `json:"data"`
196 | }
197 |
198 | func (l *LogicApi) GetPing(_ context.Context, req *LogicApiReq) (*LogicApiResp, error) {
199 | return &LogicApiResp{
200 | Code: 0,
201 | Msg: "ok",
202 | Data: req,
203 | }, nil
204 | }
205 |
206 | func TestGinEngine_GenRoute(t *testing.T) {
207 | i := New(gin.Default())
208 | group := i.Group("/enterprise/:eid/project/:pid")
209 | i.GenRoute(group, &LogicApi{}).RegisterSwaggerUI()
210 | NewCtrlC(i).Start()
211 | }
212 |
--------------------------------------------------------------------------------
/example/openapi.yaml:
--------------------------------------------------------------------------------
1 | info:
2 | title: github.com/aide-cloud/gin-plus
3 | version: v0.1.6
4 | openapi: 3.0.3
5 | paths:
6 | /info/:id:
7 | get:
8 | operationId: GetInfo
9 | responses:
10 | 200:
11 | description: ""
12 | content:
13 | application/json:
14 | schema:
15 | type: object
16 | properties:
17 | id:
18 | type: integer
19 | title: Id
20 | name:
21 | type: string
22 | title: Name
23 | parameters:
24 | - name: id
25 | in: path
26 | required: true
27 | schema:
28 | type: integer
29 | title: Id
30 | - name: name
31 | in: query
32 | schema:
33 | type: string
34 | title: Name
35 | requestBody:
36 | content:
37 | application/json:
38 | schema:
39 | type: object
40 | properties:
41 | data:
42 | type: array
43 | title: Data
44 | items:
45 | type: object
46 | post:
47 | operationId: PostInfo
48 | responses:
49 | 200:
50 | description: ""
51 | content:
52 | application/json:
53 | schema:
54 | type: object
55 | properties:
56 | id:
57 | type: integer
58 | title: Id
59 | name:
60 | type: string
61 | title: Name
62 | parameters:
63 | - name: id
64 | in: path
65 | required: true
66 | schema:
67 | type: integer
68 | title: Id
69 | - name: name
70 | in: query
71 | schema:
72 | type: string
73 | title: Name
74 | requestBody:
75 | content:
76 | application/json:
77 | schema:
78 | type: object
79 | properties:
80 | data:
81 | type: array
82 | title: Data
83 | items:
84 | type: object
85 | put:
86 | operationId: PutInfo
87 | responses:
88 | 200:
89 | description: ""
90 | content:
91 | application/json:
92 | schema:
93 | type: object
94 | properties:
95 | id:
96 | type: integer
97 | title: Id
98 | name:
99 | type: string
100 | title: Name
101 | parameters:
102 | - name: id
103 | in: path
104 | required: true
105 | schema:
106 | type: integer
107 | title: Id
108 | - name: name
109 | in: query
110 | schema:
111 | type: string
112 | title: Name
113 | requestBody:
114 | content:
115 | application/json:
116 | schema:
117 | type: object
118 | properties:
119 | data:
120 | type: array
121 | title: Data
122 | items:
123 | type: object
124 |
--------------------------------------------------------------------------------
/tracer.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/gin-gonic/gin"
7 | "go.opentelemetry.io/otel"
8 | "go.opentelemetry.io/otel/attribute"
9 | "go.opentelemetry.io/otel/exporters/jaeger"
10 | "go.opentelemetry.io/otel/sdk/resource"
11 | tracesdk "go.opentelemetry.io/otel/sdk/trace"
12 | semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
13 | oteltrace "go.opentelemetry.io/otel/trace"
14 | "gorm.io/gorm"
15 | )
16 |
17 | // 告诉编译器这个结构体实现了gorm.Plugin接口
18 | var _ gorm.Plugin = (*OpentracingPlugin)(nil)
19 |
20 | type tracingConfig struct {
21 | // URL 上报地址
22 | URL string
23 | KeyValue func(c *gin.Context) []attribute.KeyValue
24 | }
25 |
26 | type TracingOption func(*tracingConfig)
27 |
28 | // WithTracingURL 设置上报地址
29 | func WithTracingURL(url string) TracingOption {
30 | return func(c *tracingConfig) {
31 | c.URL = url
32 | }
33 | }
34 |
35 | // WithTracingKeyValueFunc 设置KeyValueFunc
36 | func WithTracingKeyValueFunc(keyValue func(c *gin.Context) []attribute.KeyValue) TracingOption {
37 | return func(c *tracingConfig) {
38 | c.KeyValue = keyValue
39 | }
40 | }
41 |
42 | func defaultKeyValueFunc(c *gin.Context) []attribute.KeyValue {
43 | return []attribute.KeyValue{
44 | attribute.String("http.method", c.Request.Method),
45 | attribute.String("http.host", c.Request.Host),
46 | attribute.String("http.user_agent", c.Request.UserAgent()),
47 | attribute.String("http.client_ip", c.ClientIP()),
48 | }
49 | }
50 |
51 | func tracerProvider(url, serviceName, environment, id string) *tracesdk.TracerProvider {
52 | // Create the Jaeger exporter
53 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
54 | if err != nil {
55 | panic(err)
56 | }
57 | tp := tracesdk.NewTracerProvider(
58 | // Always be sure to batch in production.
59 | tracesdk.WithBatcher(exp),
60 | // Record information about this application in a Resource.
61 | tracesdk.WithResource(resource.NewWithAttributes(
62 | semconv.SchemaURL,
63 | semconv.ServiceNameKey.String(serviceName),
64 | attribute.String("environment", environment),
65 | attribute.String("ID", id),
66 | )),
67 | )
68 | otel.SetTracerProvider(tp)
69 | return tp
70 | }
71 |
72 | const gormSpanKey = "__gorm_span"
73 | const gormTime = "__gorm_time"
74 |
75 | func before(db *gorm.DB) {
76 | // 先从父级spans生成子span ---> 这里命名为gorm,但实际上可以自定义
77 | // 自己喜欢的operationName
78 | ctx, span := otel.Tracer("gorm").Start(db.Statement.Context, "gorm")
79 | // 利用db实例去传递span
80 | db.InstanceSet(gormSpanKey, span)
81 | db.InstanceSet(gormTime, time.Now())
82 | // 设置context
83 | db.WithContext(ctx)
84 | }
85 |
86 | func after(db *gorm.DB) {
87 | // 从GORM的DB实例中取出span
88 | _span, isExist := db.InstanceGet(gormSpanKey)
89 | if !isExist {
90 | // 不存在就直接抛弃掉
91 | return
92 | }
93 |
94 | // 断言进行类型转换
95 | span, ok := _span.(oteltrace.Span)
96 | if !ok {
97 | return
98 | }
99 | defer span.End()
100 |
101 | // Error
102 | if db.Error != nil {
103 | span.SetAttributes(attribute.String("error", db.Error.Error()))
104 | }
105 | // sql --> 写法来源GORM V2的日志
106 | span.SetAttributes(attribute.String("sql", db.Dialector.Explain(db.Statement.SQL.String(), db.Statement.Vars...)))
107 | // rows
108 | span.SetAttributes(attribute.Int64("rows", db.RowsAffected))
109 | // elapsed
110 | _time, isExist := db.InstanceGet(gormTime)
111 | if !isExist {
112 | return
113 | }
114 | startTime, ok := _time.(time.Time)
115 | if ok {
116 | span.SetAttributes(attribute.String("elapsed", time.Since(startTime).String()))
117 | }
118 | }
119 |
120 | const (
121 | callBackBeforeName = "opentracing:before"
122 | callBackAfterName = "opentracing:after"
123 | )
124 |
125 | type OpentracingPlugin struct{}
126 |
127 | // NewOpentracingPlugin 创建一个opentracing插件
128 | func NewOpentracingPlugin() *OpentracingPlugin {
129 | return &OpentracingPlugin{}
130 | }
131 |
132 | func (op *OpentracingPlugin) Name() string {
133 | return "opentracingPlugin"
134 | }
135 |
136 | func (op *OpentracingPlugin) Initialize(db *gorm.DB) (err error) {
137 | // 开始前 - 并不是都用相同的方法,可以自己自定义
138 | if err = db.Callback().Create().Before("gorm:before_create").Register(callBackBeforeName, before); err != nil {
139 | return err
140 | }
141 | if err = db.Callback().Query().Before("gorm:query").Register(callBackBeforeName, before); err != nil {
142 | return err
143 | }
144 | if err = db.Callback().Delete().Before("gorm:before_delete").Register(callBackBeforeName, before); err != nil {
145 | return err
146 | }
147 | if err = db.Callback().Update().Before("gorm:setup_reflect_value").Register(callBackBeforeName, before); err != nil {
148 | return err
149 | }
150 | if err = db.Callback().Row().Before("gorm:row").Register(callBackBeforeName, before); err != nil {
151 | return err
152 | }
153 | if err = db.Callback().Raw().Before("gorm:raw").Register(callBackBeforeName, before); err != nil {
154 | return err
155 | }
156 |
157 | // 结束后 - 并不是都用相同的方法,可以自己自定义
158 | if err = db.Callback().Create().After("gorm:after_create").Register(callBackAfterName, after); err != nil {
159 | return err
160 | }
161 | if err = db.Callback().Query().After("gorm:after_query").Register(callBackAfterName, after); err != nil {
162 | return err
163 | }
164 | if err = db.Callback().Delete().After("gorm:after_delete").Register(callBackAfterName, after); err != nil {
165 | return err
166 | }
167 | if err = db.Callback().Update().After("gorm:after_update").Register(callBackAfterName, after); err != nil {
168 | return err
169 | }
170 | if err = db.Callback().Row().After("gorm:row").Register(callBackAfterName, after); err != nil {
171 | return err
172 | }
173 | if err = db.Callback().Raw().After("gorm:raw").Register(callBackAfterName, after); err != nil {
174 | return err
175 | }
176 | return
177 | }
178 |
--------------------------------------------------------------------------------
/openapi.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/spf13/viper"
7 | )
8 |
9 | const defaultOpenApiYaml = "openapi.yaml"
10 |
11 | type (
12 | Info struct {
13 | Title string `yaml:"title,omitempty"`
14 | Version string `yaml:"version,omitempty"`
15 | }
16 |
17 | Properties struct {
18 | Properties map[string]SchemaInfo `yaml:"properties,omitempty"`
19 | Type string `yaml:"type,omitempty"`
20 | }
21 |
22 | SchemaInfo struct {
23 | Type string `yaml:"type,omitempty"`
24 | Title string `yaml:"title,omitempty"`
25 | Format string `yaml:"format,omitempty"`
26 | Description string `yaml:"description,omitempty"`
27 | Properties map[string]SchemaInfo `yaml:"properties,omitempty"`
28 | Items Properties `yaml:"items,omitempty"`
29 | }
30 |
31 | Schema struct {
32 | Schema SchemaInfo `yaml:"schema,omitempty"`
33 | }
34 |
35 | ApiContent map[string]Schema
36 |
37 | ApiResponse struct {
38 | Description string `yaml:"description"`
39 | Content ApiContent `yaml:"content,omitempty"`
40 | }
41 |
42 | ApiRequest struct {
43 | Content ApiContent `yaml:"content,omitempty"`
44 | }
45 |
46 | Parameter struct {
47 | Name string `yaml:"name,omitempty"`
48 | In string `yaml:"in,omitempty"`
49 | Required bool `yaml:"required,omitempty"`
50 | Schema SchemaInfo `yaml:"schema,omitempty"`
51 | }
52 |
53 | ApiHttpMethod struct {
54 | OperationId string `yaml:"operationId,omitempty"`
55 | Tags []string `yaml:"tags,omitempty"`
56 | Responses map[int]ApiResponse `yaml:"responses,omitempty"`
57 | Parameters []Parameter `yaml:"parameters,omitempty"`
58 | RequestBody ApiRequest `yaml:"requestBody,omitempty"`
59 | }
60 |
61 | Path map[string]map[string]ApiHttpMethod
62 |
63 | ApiTemplate struct {
64 | Openapi string `yaml:"openapi,omitempty"`
65 | Info Info `yaml:"info,omitempty"`
66 | Paths map[string]Path `yaml:"paths,omitempty"`
67 | }
68 | )
69 |
70 | func (l *GinEngine) genOpenApiYaml() {
71 | apiYaml := defaultOpenApiYaml
72 | if l.defaultOpenApiYaml != "" && strings.HasSuffix(l.defaultOpenApiYaml, ".yaml") {
73 | apiYaml = l.defaultOpenApiYaml
74 | }
75 |
76 | // 写入之前先清空viper
77 | viper.Reset()
78 |
79 | viper.SetConfigFile(apiYaml)
80 | viper.SetConfigPermissions(0644)
81 | viper.SetConfigType("yaml")
82 | viper.Set("openapi", "3.1.3")
83 | viper.Set("info", Info{
84 | Title: l.apiConfig.Title,
85 | Version: l.apiConfig.Version,
86 | })
87 | viper.Set("paths", l.apiToYamlModel())
88 |
89 | if err := viper.WriteConfig(); err != nil {
90 | panic(err)
91 | }
92 | }
93 |
94 | func isUri(uriStr string) bool {
95 | return uriStr != "" && uriStr != "-"
96 | }
97 |
98 | func (l *GinEngine) apiToYamlModel() Path {
99 | apiRoutes := l.apiRoutes
100 |
101 | apiPath := make(Path)
102 | for url, info := range apiRoutes {
103 | if _, ok := apiPath[url]; !ok {
104 | apiPath[url] = make(map[string]ApiHttpMethod)
105 | }
106 |
107 | methodRoute := make(map[string]ApiHttpMethod)
108 | for _, route := range info {
109 | methodRoute[route.HttpMethod] = ApiHttpMethod{
110 | OperationId: route.MethodName,
111 | Tags: nil,
112 | Responses: map[int]ApiResponse{
113 | 200: {
114 | Content: map[string]Schema{
115 | "application/json": {
116 | Schema: SchemaInfo{
117 | Type: "object",
118 | Title: route.RespParams.Name,
119 | Properties: genProperties(route.RespParams.Info),
120 | },
121 | },
122 | },
123 | },
124 | },
125 | Parameters: func() []Parameter {
126 | infos := route.ReqParams.Info
127 | res := make([]Parameter, 0, len(infos))
128 | for _, fieldInfo := range infos {
129 | if fieldInfo.Tags.FormKey == "" && fieldInfo.Tags.UriKey == "" {
130 | continue
131 | }
132 |
133 | name := fieldInfo.Tags.FormKey
134 | in := "query"
135 | isUriParam := isUri(fieldInfo.Tags.UriKey)
136 | if isUriParam {
137 | name = fieldInfo.Tags.UriKey
138 | in = "path"
139 | }
140 |
141 | res = append(res, Parameter{
142 | Name: name,
143 | In: in,
144 | Required: isUriParam,
145 | Schema: SchemaInfo{
146 | Type: getTypeMap(fieldInfo.Type),
147 | Title: fieldInfo.Tags.Title,
148 | Format: fieldInfo.Tags.Format,
149 | Description: fieldInfo.Tags.Desc,
150 | },
151 | })
152 | }
153 |
154 | return res
155 | }(),
156 | RequestBody: ApiRequest{
157 | Content: map[string]Schema{
158 | "application/json": {
159 | Schema: SchemaInfo{
160 | Type: "object",
161 | Title: route.ReqParams.Name,
162 | Properties: genProperties(route.ReqParams.Info),
163 | },
164 | },
165 | },
166 | },
167 | }
168 | }
169 | apiPath[url] = methodRoute
170 | }
171 | return apiPath
172 | }
173 |
174 | func genProperties(fieldList []FieldInfo) map[string]SchemaInfo {
175 | if len(fieldList) == 0 {
176 | return nil
177 | }
178 | resp := make(map[string]SchemaInfo)
179 | for _, info := range fieldList {
180 | jsonKey := info.Tags.JsonKey
181 | if jsonKey == "-" || jsonKey == "" {
182 | continue
183 | }
184 | fieldType := getTypeMap(info.Type)
185 |
186 | schema := SchemaInfo{
187 | Type: fieldType,
188 | Title: info.Tags.Title,
189 | Format: info.Tags.Format,
190 | Description: info.Tags.Desc,
191 | }
192 |
193 | switch fieldType {
194 | case "object":
195 | schema.Properties = genProperties(info.Info)
196 | case "array":
197 | schema.Items.Properties = genProperties(info.Info)
198 | schema.Items.Type = getTypeMap(info.ChildType)
199 | }
200 |
201 | resp[info.Tags.JsonKey] = schema
202 | }
203 |
204 | return resp
205 | }
206 |
207 | // "array", "boolean", "integer", "null", "number", "object", "string"
208 | func getTypeMap(typeStr string) string {
209 | switch typeStr {
210 | case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr":
211 | return "integer"
212 | case "float32", "float64", "complex64", "complex128":
213 | return "number"
214 | case "boolean", "string", "array", "bool":
215 | return typeStr
216 | default:
217 | if strings.HasPrefix(typeStr, "[]") {
218 | return "array"
219 | }
220 | return "object"
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/new_test.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "log"
7 | "testing"
8 |
9 | "github.com/gin-gonic/gin"
10 | )
11 |
12 | type People struct {
13 | *Img
14 | }
15 |
16 | type Img struct {
17 | File *File
18 | }
19 |
20 | type File struct {
21 | }
22 |
23 | var _ IMiddleware = (*People)(nil)
24 | var _ Controller = (*People)(nil)
25 | var _ MethodeMiddleware = (*People)(nil)
26 |
27 | func (l *Img) GetImgInfo() gin.HandlerFunc {
28 | return func(ctx *gin.Context) {
29 | ctx.String(200, "GetInfo")
30 | }
31 | }
32 |
33 | func (l *File) GetFiles() gin.HandlerFunc {
34 | return func(ctx *gin.Context) {
35 | ctx.String(200, "GetFiles")
36 | }
37 | }
38 |
39 | func (p *People) GetInfo() gin.HandlerFunc {
40 | return func(ctx *gin.Context) {
41 | ctx.String(200, "GetInfo")
42 | }
43 | }
44 |
45 | func (p *People) List() gin.HandlerFunc {
46 | return func(ctx *gin.Context) {
47 | ctx.String(200, "List")
48 | }
49 | }
50 |
51 | func (p *People) PostCreateInfo(_ context.Context, _ struct{ Name string }) (string, error) {
52 | return "PostCreateInfo", errors.New("custom error")
53 | }
54 |
55 | func (p *People) PutUpdateInfo(_ context.Context, req struct{ Name string }) (struct{ Name string }, error) {
56 | return struct{ Name string }{Name: req.Name}, nil
57 | }
58 |
59 | func (p *People) Middlewares() []gin.HandlerFunc {
60 | return []gin.HandlerFunc{
61 | func(context *gin.Context) {
62 | log.Println("middleware1")
63 | },
64 | func(context *gin.Context) {
65 | log.Println("middleware2")
66 | },
67 | }
68 | }
69 |
70 | func (p *People) BasePath() string {
71 | return "/people/v1"
72 | }
73 |
74 | func (p *People) MethodeMiddlewares() map[string][]gin.HandlerFunc {
75 | return map[string][]gin.HandlerFunc{
76 | "GetInfo": {
77 | func(ctx *gin.Context) {
78 | log.Println("GetInfo middleware1")
79 | },
80 | },
81 | }
82 | }
83 |
84 | type Slice []string
85 |
86 | var _ IMiddleware = (*Slice)(nil)
87 |
88 | func (l *Slice) Middlewares() []gin.HandlerFunc {
89 | return nil
90 | }
91 |
92 | func (l *Slice) GetInfo() gin.HandlerFunc {
93 | return func(ctx *gin.Context) {
94 | ctx.String(200, "GetInfo")
95 | }
96 | }
97 |
98 | func TestNew(t *testing.T) {
99 | r := gin.Default()
100 | opts := []OptionFun{
101 | WithMiddlewares(func(ctx *gin.Context) {
102 | log.Println("main middleware")
103 | }),
104 | WithBasePath("aide-cloud"),
105 | WithHttpMethodPrefixes(HttpMethod{
106 | Prefix: "Get",
107 | Method: Get,
108 | }, HttpMethod{
109 | Prefix: "Post",
110 | Method: Post,
111 | }, HttpMethod{
112 | Prefix: "Put",
113 | Method: Put,
114 | }, HttpMethod{
115 | Prefix: "Delete",
116 | Method: Delete,
117 | }),
118 | WithControllers(&People{
119 | Img: &Img{
120 | File: &File{},
121 | },
122 | }, &Slice{}),
123 | WithRouteNamingRuleFunc(func(methodName string) string {
124 | return routeToCamel(methodName)
125 | }),
126 | WithApiConfig(ApiConfig{
127 | Title: "aide-cloud-api",
128 | Version: "v1",
129 | }),
130 | }
131 | ginInstance := New(r, opts...)
132 | _ = ginInstance.Run(":8080")
133 | }
134 |
135 | type (
136 | MyController struct {
137 | }
138 |
139 | MyControllerReq struct {
140 | Name string `form:"name"`
141 | Id uint `uri:"id"`
142 | Keyword string `form:"keyword"`
143 | }
144 |
145 | MyControllerResp struct {
146 | Name string `json:"name"`
147 | Id uint `json:"id"`
148 | Age int `json:"-"`
149 | Data any `json:"data"`
150 | }
151 | )
152 |
153 | func (l *MyController) GetInfo(_ context.Context, req MyControllerReq) (*MyControllerResp, error) {
154 | log.Println(req)
155 | return nil, nil
156 | }
157 |
158 | func TestGenApi(t *testing.T) {
159 | r := gin.Default()
160 | opts := []OptionFun{
161 | WithBasePath("aide-cloud"),
162 | WithControllers(&MyController{}),
163 | }
164 | ginInstance := New(r, opts...)
165 | ginInstance.genOpenApiYaml()
166 | }
167 |
168 | func TestGenApiRun(t *testing.T) {
169 | r := gin.Default()
170 | opts := []OptionFun{
171 | WithBasePath("aide-cloud"),
172 | WithControllers(&MyController{}),
173 | }
174 | ginInstance := New(r, opts...)
175 | _ = ginInstance.Run()
176 | }
177 |
178 | type (
179 | MidController struct {
180 | ChildMidController *ChildMidController
181 | }
182 |
183 | ChildMidController struct {
184 | }
185 | )
186 |
187 | func (l *ChildMidController) Middlewares() []gin.HandlerFunc {
188 | return []gin.HandlerFunc{
189 | func(ctx *gin.Context) {
190 | log.Println("ChildMidController")
191 | },
192 | }
193 | }
194 |
195 | func (l *ChildMidController) Info() gin.HandlerFunc {
196 | return func(ctx *gin.Context) {
197 | log.Println("Info action")
198 | }
199 | }
200 |
201 | type DetailReq struct {
202 | Id uint `uri:"id"`
203 | }
204 |
205 | type DetailResp struct {
206 | Name string `json:"name"`
207 | Id uint `json:"id"`
208 | }
209 |
210 | func (l *ChildMidController) Detail(_ context.Context, req *DetailReq) (*DetailResp, error) {
211 | log.Println("Detail")
212 | return &DetailResp{Name: "aide-cloud", Id: req.Id}, nil
213 | }
214 |
215 | func (l *MidController) Middlewares() []gin.HandlerFunc {
216 | return []gin.HandlerFunc{
217 | func(ctx *gin.Context) {
218 | log.Println("MidController")
219 | },
220 | }
221 | }
222 |
223 | func (l *MidController) GetParent() gin.HandlerFunc {
224 | return func(ctx *gin.Context) {
225 | log.Println("Parent action")
226 | }
227 | }
228 |
229 | var _ IMiddleware = (*MidController)(nil)
230 | var _ IMiddleware = (*ChildMidController)(nil)
231 |
232 | func TestGenApiRunMid(t *testing.T) {
233 | r := gin.Default()
234 | opts := []OptionFun{
235 | WithBasePath("aide-cloud"),
236 | WithControllers(&MidController{
237 | ChildMidController: &ChildMidController{},
238 | }),
239 | }
240 | ginInstance := New(r, opts...)
241 | _ = ginInstance.Run()
242 | }
243 |
244 | type (
245 | Path1 struct {
246 | Path2 *Path2
247 | }
248 | Path2 struct {
249 | }
250 | )
251 |
252 | func (p *Path2) MethodeMiddlewares() map[string][]gin.HandlerFunc {
253 | return map[string][]gin.HandlerFunc{
254 | "GetInfoByID": {
255 | func(ctx *gin.Context) {
256 | log.Println("Path2 GetInfoByID middleware1")
257 | },
258 | func(ctx *gin.Context) {
259 | log.Println("Path2 GetInfoByID middleware2")
260 | },
261 | },
262 | }
263 | }
264 |
265 | func (p *Path2) Middlewares() []gin.HandlerFunc {
266 | return []gin.HandlerFunc{
267 | func(ctx *gin.Context) {
268 | log.Println("Path2 Middlewares 1")
269 | },
270 | func(ctx *gin.Context) {
271 | log.Println("Path2 Middlewares 2")
272 | },
273 | }
274 | }
275 |
276 | func (p *Path1) Middlewares() []gin.HandlerFunc {
277 | return []gin.HandlerFunc{
278 | func(ctx *gin.Context) {
279 | log.Println("Path1 Middlewares 1")
280 | },
281 | func(ctx *gin.Context) {
282 | log.Println("Path1 Middlewares 2")
283 | },
284 | }
285 | }
286 |
287 | var _ IMiddleware = (*Path1)(nil)
288 | var _ IMiddleware = (*Path2)(nil)
289 | var _ MethodeMiddleware = (*Path2)(nil)
290 |
291 | func (p *Path1) GetInfo() gin.HandlerFunc {
292 | return func(ctx *gin.Context) {
293 | log.Println("Path1 GetInfo")
294 | }
295 | }
296 |
297 | func (p *Path1) GetInfoByID(_ context.Context, req *struct {
298 | Id uint `uri:"id"`
299 | }) (*struct {
300 | Id uint `json:"id"`
301 | }, error) {
302 | log.Println("Path1 GetInfoByID")
303 | return (*struct {
304 | Id uint `json:"id"`
305 | })(&struct{ Id uint }{Id: req.Id}), nil
306 | }
307 |
308 | func (p *Path2) GetInfo() gin.HandlerFunc {
309 | return func(ctx *gin.Context) {
310 | log.Println("Path2 GetInfo")
311 | }
312 | }
313 |
314 | func (p *Path2) GetInfoByID(_ context.Context, req *struct {
315 | Id uint `uri:"id"`
316 | }) (*struct {
317 | Id uint `json:"id"`
318 | }, error) {
319 | log.Println("Path2 GetInfoByID")
320 | return (*struct {
321 | Id uint `json:"id"`
322 | })(&struct{ Id uint }{Id: req.Id}), nil
323 | }
324 |
325 | func TestRouteGroup(t *testing.T) {
326 | New(gin.Default(), WithControllers(&Path1{Path2: &Path2{}}))
327 | }
328 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gin plus
2 |
3 | > 用于对gin框架增强, 实现根据结构体+结构体方法名实现路由注册、文档生成等功能
4 |
5 | [使用示例](https://github.com/aide-cloud/gin-plus-app)
6 |
7 | ## 安装
8 |
9 | ```shell
10 | go get -u github.com/aide-cloud/gin-plus
11 | ```
12 |
13 | ## 使用
14 |
15 | ```go
16 | package main
17 |
18 | import (
19 | "log"
20 |
21 | ginplush "github.com/aide-cloud/gin-plus"
22 |
23 | "github.com/gin-gonic/gin"
24 | )
25 |
26 | type People struct {
27 | }
28 |
29 | func (p *People) GetInfo() gin.HandlerFunc {
30 | return func(ctx *gin.Context) {
31 | ctx.String(200, "GetInfo")
32 | }
33 | }
34 |
35 | func (p *People) Middlewares() []gin.HandlerFunc {
36 | return []gin.HandlerFunc{
37 | func(context *gin.Context) {
38 | log.Println("middleware1")
39 | },
40 | func(context *gin.Context) {
41 | log.Println("middleware2")
42 | },
43 | }
44 | }
45 |
46 | func main() {
47 | r := gin.Default()
48 | ginInstance := ginplush.New(r, ginplush.WithControllers(&People{}))
49 | ginInstance.Run(":8080")
50 | }
51 |
52 | ```
53 |
54 | ## 自定义
55 |
56 | ```go
57 | package main
58 |
59 | import (
60 | "log"
61 |
62 | ginplush "github.com/aide-cloud/gin-plus"
63 |
64 | "github.com/gin-gonic/gin"
65 | )
66 |
67 | // People controller, 会根据该controller的方法名注册路由
68 | type People struct {
69 | // 内联controller, 方法注册到父级
70 | *Img
71 | }
72 |
73 | // Img controller, 会根据该controller的方法名注册路由
74 | type Img struct {
75 | File *File
76 | }
77 |
78 | // File controller, 会根据该controller的方法名注册路由
79 | type File struct {
80 | }
81 |
82 | // GetImgInfo Get /imgInfo
83 | func (l *Img) GetImgInfo() gin.HandlerFunc {
84 | return func(ctx *gin.Context) {
85 | ctx.String(200, "GetInfo")
86 | }
87 | }
88 |
89 | // GetFiles Get /files
90 | func (l *File) GetFiles() gin.HandlerFunc {
91 | return func(ctx *gin.Context) {
92 | ctx.String(200, "GetFiles")
93 | }
94 | }
95 |
96 | // GetInfo Get /info
97 | func (p *People) GetInfo() gin.HandlerFunc {
98 | return func(ctx *gin.Context) {
99 | ctx.String(200, "GetInfo")
100 | }
101 | }
102 |
103 | // List Get /list
104 | func (p *People) List() gin.HandlerFunc {
105 | return func(ctx *gin.Context) {
106 | ctx.String(200, "List")
107 | }
108 | }
109 |
110 | // Middlewares 模块下公共中间件
111 | func (p *People) Middlewares() []gin.HandlerFunc {
112 | return []gin.HandlerFunc{
113 | func(context *gin.Context) {
114 | log.Println("middleware1")
115 | },
116 | func(context *gin.Context) {
117 | log.Println("middleware2")
118 | },
119 | }
120 | }
121 |
122 | // BasePath People模块下公共路由前缀
123 | func (p *People) BasePath() string {
124 | return "/people/v1"
125 | }
126 |
127 | // MethoderMiddlewares People各个方法的中间件
128 | func (p *People) MethoderMiddlewares() map[string][]gin.HandlerFunc {
129 | return map[string][]gin.HandlerFunc{
130 | "GetInfo": {
131 | func(ctx *gin.Context) {
132 | log.Println("GetInfo middleware1")
133 | },
134 | },
135 | }
136 | }
137 |
138 | type Slice []string
139 |
140 | func (l *Slice) Middlewares() []gin.HandlerFunc {
141 | return nil
142 | }
143 |
144 | func (l *Slice) GetInfo() gin.HandlerFunc {
145 | return func(ctx *gin.Context) {
146 | ctx.String(200, "GetInfo")
147 | }
148 | }
149 |
150 | func main() {
151 | r := gin.Default()
152 | opts := []ginplush.Option{
153 | // 注册全局路由
154 | ginplush.WithMiddlewares(func(ctx *gin.Context) {
155 | log.Println("main middleware")
156 | }),
157 | // 设置基础的路由前缀, 会在每个路由前面加上该前缀
158 | ginplush.WithBasePath("aide-cloud"),
159 | // 设置路由前缀, 只识别这个列表的函数, 其他函数统一以defaultHttpMethod为http方法注册
160 | ginplush.WithHttpMethodPrefixes(ginplush.Get, ginplush.Post),
161 | // 无前缀的函数默认使用的http方法
162 | ginplush.WithDefaultHttpMethod(ginplush.Post),
163 | // 路由controller, 会根据该controller的方法名注册路由
164 | ginplush.WithControllers(&People{
165 | Img: &Img{
166 | File: &File{},
167 | },
168 | }, &Slice{}),
169 | }
170 | ginInstance := ginplush.New(r, opts...)
171 | ginInstance.Run(":8080")
172 | }
173 | ```
174 |
175 | * 运行截图
176 |
177 | 
178 |
179 | ## v0.0.6+之后
180 |
181 | * 增加新的方法格式注入
182 |
183 | 除了上述的直接实现`gin.HandlerFunc`类型的方法可以注入之外, 从v0.0.6版本开始, 新增了`func(ctx context.Context, req *ApiReq) (*ApiResp, error)`格式的方法也能注入, 这里的req和resp可以是结构体, 也可以是结构体指针类型, 入参第一参数必须为context.Context, 出参只允许两个返回值, 且第二返回值必须为error类型
184 |
185 | 如下所示:
186 |
187 | 我们定义了一个Api的注入对象, 并增加了CRUD的四个方法, 这四个方法都为上面提到的函数格式, 完成这些后, 通过`WithControllers`方法把该对象注入到ginplus中, 从而实现`gin`路由的注册和`api`文档生成, 启动时候, 会在当前目录下生成一个`openapi.yaml`文件, 该文件就是你的注入对象所生成api文档
188 |
189 | 当然, 我们也提供了关闭生成api文档功能的开关, `ApiConfig`的`GenApiEnable`属性为`false`时候, 会关闭文档生成和文档预览功能, 通过`WithApiConfig`完成控制
190 |
191 | ```go
192 | package main
193 |
194 | import (
195 | "context"
196 | "log"
197 |
198 | ginplush "github.com/aide-cloud/gin-plus"
199 |
200 | "github.com/gin-gonic/gin"
201 | )
202 |
203 | type (
204 | Api struct {
205 | }
206 |
207 | ApiDetailReq struct {
208 | Id uint `uri:"id"`
209 | }
210 |
211 | ApiDetailResp struct {
212 | Id uint `json:"id"`
213 | Name string `json:"name"`
214 | Remark string `json:"remark"`
215 | }
216 |
217 | ApiListReq struct {
218 | Current int `form:"current"`
219 | Size int `form:"size"`
220 | Keryworld string `form:"keyworld"`
221 | }
222 | ApiListResp struct {
223 | Total int64 `json:"total"`
224 | Current int `json:"current"`
225 | Size int `json:"size"`
226 | List []*ApiInfoItem `json:"list"`
227 | }
228 |
229 | ApiInfoItem struct {
230 | Name string `json:"name"`
231 | Id uint `json:"id"`
232 | Remark string `json:"remark"`
233 | }
234 |
235 | ApiUpdateReq struct {
236 | Id uint `uri:"id"`
237 | Name string `json:"name"`
238 | Remark string `json:"remark"`
239 | }
240 | ApiUpdateResp struct {
241 | Id uint `json:"id"`
242 | }
243 |
244 | DelApiReq struct {
245 | Id uint `uri:"id"`
246 | }
247 |
248 | DelApiResp struct {
249 | Id uint `json:"id"`
250 | }
251 | )
252 |
253 | func (l *Api) GetDetail(ctx context.Context, req *ApiDetailReq) (*ApiDetailResp, error) {
254 | log.Println("Api.GetDetail")
255 | return &ApiDetailResp{
256 | Id: req.Id,
257 | Name: "demo",
258 | Remark: "hello world",
259 | }, nil
260 | }
261 |
262 | func (l *Api) GetList(ctx context.Context, req *ApiListReq) (*ApiListResp, error) {
263 | log.Println("Api.GetList", req)
264 | return &ApiListResp{
265 | Total: 100,
266 | Current: req.Current,
267 | Size: req.Size,
268 | List: []*ApiInfoItem{
269 | {
270 | Id: 10,
271 | Name: "demo",
272 | Remark: "hello world",
273 | },
274 | },
275 | }, nil
276 | }
277 |
278 | func (l *Api) UpdateInfo(ctx context.Context, req *ApiUpdateReq) (*ApiUpdateResp, error) {
279 | log.Println("Api.UpdateInfo")
280 | return &ApiUpdateResp{Id: req.Id}, nil
281 | }
282 |
283 | func (l *Api) DeleteInfo(ctx context.Context, req *DelApiReq) (*DelApiResp, error) {
284 | log.Println("Api.DeleteInfo")
285 | return &DelApiResp{Id: req.Id}, nil
286 | }
287 |
288 | func main() {
289 | r := gin.Default()
290 | opts := []ginplush.Option{
291 | // 路由controller, 会根据该controller的方法名注册路由
292 | ginplush.WithControllers(&Api{}),
293 | }
294 | ginInstance := ginplush.New(r, opts...)
295 | ginInstance.Run(":8080")
296 | }
297 | ```
298 |
299 | ## graphql
300 |
301 | ```go
302 | package ginplus
303 |
304 | import (
305 | "embed"
306 | "testing"
307 |
308 | "github.com/gin-gonic/gin"
309 | )
310 |
311 | // Content holds all the SDL file content.
312 | //
313 | //go:embed sdl
314 | var content embed.FS
315 |
316 | type Root struct{}
317 |
318 | func (r *Root) Ping() string {
319 | return "pong"
320 | }
321 |
322 | func TestGraphql(t *testing.T) {
323 | instance := New(gin.Default(), WithGraphqlConfig(GraphqlConfig{
324 | Enable: true,
325 | HandlePath: "/graphql",
326 | ViewPath: "/graphql",
327 | Root: &Root{},
328 | Content: content,
329 | }))
330 |
331 | instance.Run(":8080")
332 | }
333 | ```
334 |
335 | 
336 |
337 | 
338 |
339 |
--------------------------------------------------------------------------------
/util.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "fmt"
5 | "path"
6 | "reflect"
7 | "strings"
8 |
9 | "github.com/gin-gonic/gin"
10 | )
11 |
12 | const (
13 | GinHandleFunc = "gin.HandlerFunc"
14 | )
15 |
16 | // 判断该方法返回值是否为 gin.HandlerFunc类型
17 | func isHandlerFunc(t reflect.Type) bool {
18 | if t.Kind() != reflect.Func {
19 | return false
20 | }
21 | if t.NumOut() != 1 {
22 | return false
23 | }
24 | return t.Out(0).String() == GinHandleFunc
25 | }
26 |
27 | // isCallBack 判断是否为CallBack类型
28 | func isCallBack(t reflect.Type) (reflect.Type, reflect.Type, bool) {
29 | // 通过反射获取方法的返回值类型
30 | if t.Kind() != reflect.Func {
31 | return nil, nil, false
32 | }
33 |
34 | if t.NumIn() != 3 || t.NumOut() != 2 {
35 | return nil, nil, false
36 | }
37 |
38 | if t.Out(1).String() != "error" {
39 | return nil, nil, false
40 | }
41 |
42 | if t.In(1).String() != "context.Context" {
43 | return nil, nil, false
44 | }
45 |
46 | // new一个out 0的实例和in 2的实例
47 | req := t.In(2)
48 | resp := t.Out(0)
49 |
50 | return req, resp, true
51 | }
52 |
53 | func isNil(value interface{}) bool {
54 | // 使用反射获取值的类型和值
55 | val := reflect.ValueOf(value)
56 |
57 | // 检查值的类型
58 | switch val.Kind() {
59 | case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func:
60 | // 对于指针、接口、切片、映射、通道和函数类型,使用IsNil()方法来检查是否为nil
61 | return val.IsNil()
62 | default:
63 | // 对于其他类型,无法直接检查是否为nil
64 | return false
65 | }
66 | }
67 |
68 | func (l *GinEngine) GenRoute(parentGroup *gin.RouterGroup, controller any) *GinEngine {
69 | if isNil(controller) {
70 | logger.Warn("controller is nil")
71 | return l
72 | }
73 | l.genRoute(parentGroup, controller, false)
74 | return l
75 | }
76 |
77 | func (l *GinEngine) genRoute(parentGroup *gin.RouterGroup, controller any, skipAnonymous bool) {
78 | if isNil(controller) {
79 | return
80 | }
81 | t := reflect.TypeOf(controller)
82 |
83 | tmp := t
84 | for tmp.Kind() == reflect.Ptr {
85 | if tmp == nil {
86 | return
87 | }
88 | tmp = tmp.Elem()
89 | }
90 |
91 | if !isPublic(tmp.Name()) {
92 | return
93 | }
94 |
95 | var middlewares []gin.HandlerFunc
96 | mid, isMid := isMiddleware(controller)
97 | if isMid {
98 | //Middlewares方法返回的是gin.HandlerFunc类型的切片, 中间件
99 | middlewares = mid.Middlewares()
100 | }
101 |
102 | basePath := l.routeNamingRuleFunc(tmp.Name())
103 | ctrl, isCtrl := isController(controller)
104 | if isCtrl {
105 | basePath = ctrl.BasePath()
106 | }
107 | parentRouteGroup := parentGroup
108 | if parentRouteGroup == nil {
109 | parentRouteGroup = l.Group("/")
110 | }
111 | routeGroup := parentRouteGroup.Group(path.Join(basePath), middlewares...)
112 |
113 | methodMiddlewaresMap := make(map[string][]gin.HandlerFunc)
114 | methodMid, privateMidOk := isMethodMiddleware(controller)
115 | if privateMidOk {
116 | methodMiddlewaresMap = methodMid.MethodeMiddlewares()
117 | }
118 |
119 | if !skipAnonymous {
120 | for i := 0; i < t.NumMethod(); i++ {
121 | methodName := t.Method(i).Name
122 | if !isPublic(methodName) {
123 | continue
124 | }
125 |
126 | route := l.parseRoute(methodName)
127 | if route == nil {
128 | continue
129 | }
130 |
131 | privateMid := methodMiddlewaresMap[methodName]
132 |
133 | // 接口私有中间件
134 | route.Handles = append(route.Handles, privateMid...)
135 |
136 | if isHandlerFunc(t.Method(i).Type) {
137 | // 具体的action
138 | handleFunc := t.Method(i).Func.Call([]reflect.Value{reflect.ValueOf(controller)})[0].Interface().(gin.HandlerFunc)
139 | route.Handles = append(route.Handles, handleFunc)
140 | routeGroup.Handle(strings.ToUpper(route.HttpMethod), route.Path, route.Handles...)
141 | continue
142 | }
143 |
144 | // 判断是否为CallBack类型
145 | req, resp, isCb := isCallBack(t.Method(i).Type)
146 | if isCb {
147 | // 生成路由openAPI数据
148 | l.genOpenAPI(routeGroup, req, resp, route, methodName)
149 | // 注册路由回调函数
150 | handleFunc := l.defaultHandler(controller, t.Method(i), req)
151 | l.registerCallHandler(route, routeGroup, handleFunc)
152 | continue
153 | }
154 | }
155 | }
156 |
157 | l.genStructRoute(routeGroup, controller)
158 | }
159 |
160 | // 生成openAPI数据
161 | func (l *GinEngine) genOpenAPI(group *gin.RouterGroup, req, resp reflect.Type, route *Route, methodName string) {
162 | reqName := req.Name()
163 | respName := resp.Name()
164 | reqTagInfo := getTag(req)
165 | apiRoute := ApiRoute{
166 | Path: route.Path,
167 | HttpMethod: strings.ToLower(route.HttpMethod),
168 | MethodName: methodName,
169 | ReqParams: Field{
170 | Name: reqName,
171 | Info: reqTagInfo,
172 | },
173 | RespParams: Field{
174 | Name: respName,
175 | Info: getTag(resp),
176 | },
177 | }
178 |
179 | // 处理Uri参数
180 | for _, tagInfo := range reqTagInfo {
181 | uriKey := tagInfo.Tags.UriKey
182 | skip := tagInfo.Tags.Skip
183 | if uriKey != "" && uriKey != "-" && skip != "true" {
184 | route.Path = path.Join(route.Path, fmt.Sprintf(":%s", uriKey))
185 | }
186 | }
187 |
188 | apiPath := path.Join(group.BasePath(), route.Path)
189 | apiRoute.Path = apiPath
190 |
191 | if _, ok := l.apiRoutes[apiPath]; !ok {
192 | l.apiRoutes[apiPath] = make([]ApiRoute, 0, 1)
193 | }
194 | l.apiRoutes[apiPath] = append(l.apiRoutes[apiPath], apiRoute)
195 | }
196 |
197 | // registerCallHandler 注册回调函数
198 | func (l *GinEngine) registerCallHandler(route *Route, routeGroup *gin.RouterGroup, handleFunc gin.HandlerFunc) {
199 | // 具体的action
200 | route.Handles = append(route.Handles, handleFunc)
201 | routeGroup.Handle(strings.ToUpper(route.HttpMethod), route.Path, route.Handles...)
202 | }
203 |
204 | // genStructRoute 递归注册结构体路由
205 | func (l *GinEngine) genStructRoute(parentGroup *gin.RouterGroup, controller any) {
206 | if isNil(controller) {
207 | return
208 | }
209 | tmp := reflect.TypeOf(controller)
210 | for tmp.Kind() == reflect.Ptr {
211 | if tmp == nil {
212 | return
213 | }
214 | tmp = tmp.Elem()
215 | }
216 | if isStruct(tmp) {
217 | // 递归获取内部的controller
218 | for i := 0; i < tmp.NumField(); i++ {
219 | field := tmp.Field(i)
220 | for field.Type.Kind() == reflect.Ptr {
221 | field.Type = field.Type.Elem()
222 | }
223 | if !isStruct(field.Type) {
224 | continue
225 | }
226 |
227 | if !isPublic(field.Name) {
228 | continue
229 | }
230 |
231 | newController := reflect.ValueOf(controller).Elem().Field(i).Interface()
232 | // 判断field值是否为nil
233 | if isNil(newController) {
234 | continue
235 | }
236 |
237 | l.genRoute(parentGroup, newController, field.Anonymous)
238 | }
239 | }
240 | }
241 |
242 | // isPublic 判断是否为公共方法
243 | func isPublic(name string) bool {
244 | if len(name) == 0 {
245 | return false
246 | }
247 |
248 | first := name[0]
249 | if first < 'A' || first > 'Z' {
250 | return false
251 | }
252 |
253 | return true
254 | }
255 |
256 | // parseRoute 从方法名称中解析出路由和请求方式
257 | func (l *GinEngine) parseRoute(methodName string) *Route {
258 | method := ""
259 | routePath := ""
260 |
261 | for prefix, httpMethodKey := range l.httpMethodPrefixes {
262 | if strings.HasPrefix(methodName, prefix) {
263 | method = strings.ToLower(httpMethodKey.key)
264 | routePath = strings.TrimPrefix(methodName, prefix)
265 | if routePath == "" {
266 | routePath = strings.ToLower(methodName)
267 | }
268 | break
269 | }
270 | }
271 |
272 | if method == "" || routePath == "" {
273 | return nil
274 | }
275 |
276 | return &Route{
277 | Path: path.Join("/", l.routeNamingRuleFunc(routePath)),
278 | HttpMethod: method,
279 | }
280 | }
281 |
282 | // routeToCamel 将路由转换为驼峰命名
283 | func routeToCamel(route string) string {
284 | if route == "" {
285 | return ""
286 | }
287 |
288 | // 首字母小写
289 | if route[0] >= 'A' && route[0] <= 'Z' {
290 | route = string(route[0]+32) + route[1:]
291 | }
292 |
293 | return route
294 | }
295 |
296 | // isMiddleware 判断是否为Controller类型
297 | func isMiddleware(c any) (IMiddleware, bool) {
298 | mid, ok := c.(IMiddleware)
299 | return mid, ok
300 | }
301 |
302 | // isController 判断是否为Controller类型
303 | func isController(c any) (Controller, bool) {
304 | ctrl, ok := c.(Controller)
305 | return ctrl, ok
306 | }
307 |
308 | // isMethodMiddleware 判断是否为MethodMiddleware类型
309 | func isMethodMiddleware(c any) (MethodeMiddleware, bool) {
310 | mid, ok := c.(MethodeMiddleware)
311 | return mid, ok
312 | }
313 |
314 | // isStruct 判断是否为struct类型
315 | func isStruct(t reflect.Type) bool {
316 | tmp := t
317 | for tmp.Kind() == reflect.Ptr {
318 | if tmp == nil {
319 | return false
320 | }
321 | tmp = tmp.Elem()
322 | }
323 | return tmp.Kind() == reflect.Struct
324 | }
325 |
--------------------------------------------------------------------------------
/middleware.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "sync"
7 | "time"
8 |
9 | "github.com/gin-gonic/gin"
10 | "go.opentelemetry.io/otel"
11 | "go.opentelemetry.io/otel/attribute"
12 | semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
13 | oteltrace "go.opentelemetry.io/otel/trace"
14 | "go.uber.org/zap"
15 | )
16 |
17 | type Middleware struct {
18 | resp IResponse
19 | tracing oteltrace.TracerProvider
20 | serverName string
21 | id string
22 | env string
23 | }
24 |
25 | type MiddlewareOption func(*Middleware)
26 |
27 | // NewMiddleware 创建中间件
28 | func NewMiddleware(opts ...MiddlewareOption) *Middleware {
29 | id, _ := os.Hostname()
30 | mid := &Middleware{
31 | resp: NewResponse(),
32 | serverName: "gin-plus",
33 | id: id,
34 | env: "default",
35 | }
36 |
37 | for _, opt := range opts {
38 | opt(mid)
39 | }
40 |
41 | return mid
42 | }
43 |
44 | // WithResponse 设置响应
45 | func WithResponse(resp IResponse) MiddlewareOption {
46 | return func(m *Middleware) {
47 | m.resp = resp
48 | }
49 | }
50 |
51 | // WithServerName 设置服务名称
52 | func WithServerName(serverName string) MiddlewareOption {
53 | return func(m *Middleware) {
54 | m.serverName = serverName
55 | }
56 | }
57 |
58 | // WithID 设置服务ID
59 | func WithID(id string) MiddlewareOption {
60 | return func(m *Middleware) {
61 | m.id = id
62 | }
63 | }
64 |
65 | // WithEnv 设置环境
66 | func WithEnv(env string) MiddlewareOption {
67 | return func(m *Middleware) {
68 | m.env = env
69 | }
70 | }
71 |
72 | // Cors 直接放行所有跨域请求并放行所有 OPTIONS 方法
73 | func (l *Middleware) Cors(headers ...map[string]string) gin.HandlerFunc {
74 | return func(c *gin.Context) {
75 | ctx := c.Request.Context()
76 | tracerSpan, _ := c.Get("span")
77 | span, ok := tracerSpan.(oteltrace.Span)
78 | if ok {
79 | ctx, span = span.TracerProvider().Tracer("IMiddleware.Cors").Start(ctx, "IMiddleware.Cors")
80 | defer span.End()
81 | }
82 | method := c.Request.Method
83 | origin := c.Request.Header.Get("Origin")
84 | if origin == "null" || origin == "" {
85 | origin = "*"
86 | }
87 | c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
88 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id")
89 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
90 | c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, New-Token, New-Expires-At")
91 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
92 |
93 | for _, header := range headers {
94 | for k, v := range header {
95 | c.Writer.Header().Set(k, v)
96 | }
97 | }
98 |
99 | // 放行所有OPTIONS方法
100 | if method == "OPTIONS" {
101 | c.Writer.Header().Set("Access-Control-Max-Age", "3600")
102 | l.resp.Response(c, nil, nil)
103 | return
104 | }
105 | // 处理请求
106 | c.Next()
107 | }
108 | }
109 |
110 | type InterceptorConfig struct {
111 | // IP 如果IP不为空, 则不允许该IP访问, 否则所有IP都不允许访问
112 | IPList []string
113 | Method string
114 | Path string
115 | Msg any
116 | }
117 |
118 | // Interceptor 拦截器, 拦截指定API, 用于控制API当下不允许访问
119 | func (l *Middleware) Interceptor(configs ...InterceptorConfig) gin.HandlerFunc {
120 | return func(c *gin.Context) {
121 | ctx := c.Request.Context()
122 | tracerSpan, _ := c.Get("span")
123 | span, ok := tracerSpan.(oteltrace.Span)
124 | if ok {
125 | ctx, span = span.TracerProvider().Tracer("IMiddleware.Interceptor").Start(ctx, "IMiddleware.Interceptor")
126 | defer span.End()
127 | }
128 | for _, config := range configs {
129 | if config.Method == c.Request.Method && config.Path == c.Request.URL.Path {
130 | if len(config.IPList) != 0 {
131 | clientIP := c.ClientIP()
132 | for _, ip := range config.IPList {
133 | if ip == clientIP {
134 | l.resp.Response(c, config.Msg, nil)
135 | return
136 | }
137 | }
138 | } else {
139 | l.resp.Response(c, config.Msg, nil)
140 | return
141 | }
142 | }
143 | }
144 | c.Next()
145 | }
146 | }
147 |
148 | type TokenBucket struct {
149 | capacity int64 // 桶的容量
150 | rate float64 // 令牌放入速率
151 | tokens float64 // 当前令牌数量
152 | lastToken time.Time // 上一次放令牌的时间
153 | mtx sync.Mutex // 互斥锁
154 | }
155 |
156 | // Allow 判断是否允许请求
157 | func (tb *TokenBucket) Allow() bool {
158 | tb.mtx.Lock()
159 | defer tb.mtx.Unlock()
160 | now := time.Now()
161 | // 计算需要放的令牌数量
162 | tb.tokens = tb.tokens + tb.rate*now.Sub(tb.lastToken).Seconds()
163 | if tb.tokens > float64(tb.capacity) {
164 | tb.tokens = float64(tb.capacity)
165 | }
166 | // 判断是否允许请求
167 | if tb.tokens >= 1 {
168 | tb.tokens--
169 | tb.lastToken = now
170 | return true
171 | }
172 |
173 | return false
174 | }
175 |
176 | // IpLimit IP限制, 用于控制API的访问频率
177 | func (l *Middleware) IpLimit(capacity int64, rate float64) gin.HandlerFunc {
178 | syncTokenMap := sync.Map{}
179 | return func(c *gin.Context) {
180 | ctx := c.Request.Context()
181 | tracerSpan, _ := c.Get("span")
182 | span, ok := tracerSpan.(oteltrace.Span)
183 | if ok {
184 | ctx, span = span.TracerProvider().Tracer("IMiddleware.IpLimit").Start(ctx, "IMiddleware.IpLimit")
185 | defer span.End()
186 | }
187 | clientIP := c.ClientIP()
188 | if _, ok := syncTokenMap.Load(clientIP); !ok {
189 | clientTb := TokenBucket{
190 | capacity: capacity,
191 | rate: rate,
192 | tokens: float64(capacity) * rate,
193 | lastToken: time.Now(),
194 | }
195 | syncTokenMap.Store(clientIP, &clientTb)
196 | }
197 |
198 | tb, ok := syncTokenMap.Load(clientIP)
199 | if !ok {
200 | logger.Error("ip limit error, not found token bucket")
201 | l.resp.Response(c, nil, nil)
202 | return
203 | }
204 |
205 | if !tb.(*TokenBucket).Allow() {
206 | l.resp.Response(c, nil, nil)
207 | return
208 | }
209 | c.Next()
210 | }
211 | }
212 |
213 | // Tracing 链路追踪
214 | func (l *Middleware) Tracing(url string, opts ...TracingOption) gin.HandlerFunc {
215 | cfg := &tracingConfig{
216 | KeyValue: defaultKeyValueFunc,
217 | URL: url,
218 | }
219 | for _, opt := range opts {
220 | opt(cfg)
221 | }
222 |
223 | l.tracing = tracerProvider(cfg.URL, l.serverName, l.env, l.id)
224 |
225 | return func(c *gin.Context) {
226 | spanOpts := []oteltrace.SpanStartOption{
227 | oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(l.serverName, c.FullPath(), c.Request)...),
228 | oteltrace.WithSpanKind(oteltrace.SpanKindServer),
229 | }
230 | spanName := c.FullPath()
231 | if spanName == "" {
232 | spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method)
233 | }
234 |
235 | ctx, span := otel.Tracer("IMiddleware.Tracing").Start(c, spanName, spanOpts...)
236 | defer span.End()
237 | c.Set("span", span)
238 |
239 | c.Request = c.Request.WithContext(ctx)
240 | sc := span.SpanContext()
241 | if sc.HasTraceID() {
242 | c.Header("trace_id", sc.TraceID().String())
243 | } else {
244 | c.Header("trace_id", "not-trace")
245 | }
246 |
247 | c.Request.Header.Set("trace_id", span.SpanContext().TraceID().String())
248 | c.Request.Header.Set("span_id", span.SpanContext().SpanID().String())
249 | c.Next()
250 | kvFunc := cfg.KeyValue
251 | if kvFunc == nil {
252 | kvFunc = func(c *gin.Context) []attribute.KeyValue {
253 | return nil
254 | }
255 | }
256 | kvs := kvFunc(c)
257 | span.SetAttributes(kvs...)
258 |
259 | // 上报错误
260 | if len(c.Errors) > 0 {
261 | span.SetAttributes(attribute.String("gin.errors", c.Errors.String()))
262 | }
263 |
264 | // 上报状态码
265 | status := c.Writer.Status()
266 | attrs := semconv.HTTPAttributesFromHTTPStatusCode(status)
267 | spanStatus, spanMessage := semconv.SpanStatusFromHTTPStatusCode(status)
268 | span.SetAttributes(attrs...)
269 | span.SetStatus(spanStatus, spanMessage)
270 | }
271 | }
272 |
273 | // Logger 日志
274 | func (l *Middleware) Logger(timeLayout ...string) gin.HandlerFunc {
275 | layout := time.RFC3339
276 | if len(timeLayout) > 0 {
277 | layout = timeLayout[0]
278 | }
279 | return func(c *gin.Context) {
280 | startTime := time.Now()
281 | c.Next()
282 | endTime := time.Now()
283 | latencyTime := endTime.Sub(startTime)
284 |
285 | reqMethod := c.Request.Method
286 | reqUri := c.Request.RequestURI
287 | statusCode := c.Writer.Status()
288 | clientIP := c.ClientIP()
289 | kv := []zap.Field{
290 | zap.String("timestamp", time.Now().Format(layout)),
291 | zap.String("start_time", startTime.Format(layout)),
292 | zap.String("end_time", endTime.Format(layout)),
293 | zap.String("client_ip", clientIP),
294 | zap.Int("status_code", statusCode),
295 | zap.String("req_method", reqMethod),
296 | zap.String("req_uri", reqUri),
297 | zap.String("latency_time", latencyTime.String()),
298 | }
299 |
300 | ctx := c.Request.Context()
301 |
302 | if l.tracing != nil {
303 | tracerSpan, _ := c.Get("span")
304 | span, ok := tracerSpan.(oteltrace.Span)
305 | if ok {
306 | _, span := span.TracerProvider().Tracer("IMiddleware.Logger").Start(ctx, "IMiddleware.Logger")
307 | defer span.End()
308 | span.SetAttributes(attribute.String("latency_time", latencyTime.String()))
309 | spanCtx := span.SpanContext()
310 | traceID := ""
311 | if spanCtx.HasTraceID() {
312 | traceID = spanCtx.TraceID().String()
313 | }
314 |
315 | spanID := ""
316 | if spanCtx.HasSpanID() {
317 | spanID = spanCtx.SpanID().String()
318 | }
319 | kv = append(kv, zap.String("trace_id", traceID), zap.String("span_id", spanID))
320 | }
321 | }
322 |
323 | logger.Info(l.serverName, kv...)
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/ref_test.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | "time"
7 |
8 | "gorm.io/gorm"
9 | )
10 |
11 | func Test_getTag(t *testing.T) {
12 | type My struct {
13 | Name string `json:"name"`
14 | Id uint `uri:"id"`
15 | Keyword string `form:"keyword"`
16 | }
17 |
18 | fieldList := getTag(reflect.TypeOf(&My{}))
19 |
20 | t.Log(fieldList)
21 | }
22 |
23 | func Test_getTag2(t *testing.T) {
24 | var b bool
25 | t.Logf("%T", b)
26 | }
27 |
28 | type PromStrategy struct {
29 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"`
30 | GroupID int32 `gorm:"column:group_id;type:int unsigned;not null;comment:所属规则组ID" json:"group_id"` // 所属规则组ID
31 | Alert string `gorm:"column:alert;type:varchar(64);not null;comment:规则名称" json:"alert"` // 规则名称
32 | Expr string `gorm:"column:expr;type:text;not null;comment:prom ql" json:"expr"` // prom ql
33 | For string `gorm:"column:for;type:varchar(64);not null;default:10s;comment:持续时间" json:"for"` // 持续时间
34 | Labels string `gorm:"column:labels;type:json;not null;comment:标签" json:"labels"` // 标签
35 | Annotations string `gorm:"column:annotations;type:json;not null;comment:告警文案" json:"annotations"` // 告警文案
36 | AlertLevelID int32 `gorm:"column:alert_level_id;type:int;not null;index:idx__alert_level_id,priority:1;comment:告警等级dict ID" json:"alert_level_id"` // 告警等级dict ID
37 | Status int32 `gorm:"column:status;type:tinyint;not null;default:1;comment:启用状态: 1启用;2禁用" json:"status"` // 启用状态: 1启用;2禁用
38 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
39 | UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
40 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间
41 | AlarmPages []*PromAlarmPage `gorm:"References:ID;foreignKey:ID;joinForeignKey:PromStrategyID;joinReferences:AlarmPageID;many2many:prom_strategy_alarm_pages" json:"alarm_pages"`
42 | Categories []*PromDict `gorm:"References:ID;foreignKey:ID;joinForeignKey:PromStrategyID;joinReferences:DictID;many2many:prom_strategy_categories" json:"categories"`
43 | AlertLevel *PromDict `gorm:"foreignKey:AlertLevelID" json:"alert_level"`
44 | GroupInfo *PromGroup `gorm:"foreignKey:GroupID" json:"group_info"`
45 | }
46 |
47 | type PromGroup struct {
48 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"`
49 | Name string `gorm:"column:name;type:varchar(64);not null;comment:规则组名称" json:"name"` // 规则组名称
50 | StrategyCount int64 `gorm:"column:strategy_count;type:bigint;not null;comment:规则数量" json:"strategy_count"` // 规则数量
51 | Status int32 `gorm:"column:status;type:tinyint;not null;default:1;comment:启用状态1:启用;2禁用" json:"status"` // 启用状态1:启用;2禁用
52 | Remark string `gorm:"column:remark;type:varchar(255);not null;comment:描述信息" json:"remark"` // 描述信息
53 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
54 | UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
55 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间
56 | PromStrategies []*PromStrategy `gorm:"foreignKey:GroupID" json:"prom_strategies"`
57 | Categories []*PromDict `gorm:"References:ID;foreignKey:ID;joinForeignKey:PromGroupID;joinReferences:DictID;many2many:prom_group_categories" json:"categories"`
58 | }
59 |
60 | type PromDict struct {
61 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"`
62 | Name string `gorm:"column:name;type:varchar(64);not null;uniqueIndex:idx__name__category,priority:1;comment:字典名称" json:"name"` // 字典名称
63 | Category int32 `gorm:"column:category;type:tinyint;not null;uniqueIndex:idx__name__category,priority:2;index:idx__category,priority:1;comment:字典类型" json:"category"` // 字典类型
64 | Color string `gorm:"column:color;type:varchar(32);not null;default:#165DFF;comment:字典tag颜色" json:"color"` // 字典tag颜色
65 | Status int32 `gorm:"column:status;type:tinyint;not null;default:1;comment:状态" json:"status"` // 状态
66 | Remark string `gorm:"column:remark;type:varchar(255);not null;comment:字典备注" json:"remark"` // 字典备注
67 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
68 | UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
69 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间
70 | }
71 |
72 | type PromAlarmPage struct {
73 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"`
74 | Name string `gorm:"column:name;type:varchar(64);not null;uniqueIndex:idx__name,priority:1;comment:报警页面名称" json:"name"` // 报警页面名称
75 | Remark string `gorm:"column:remark;type:varchar(255);not null;comment:描述信息" json:"remark"` // 描述信息
76 | Icon string `gorm:"column:icon;type:varchar(1024);not null;comment:图表" json:"icon"` // 图表
77 | Color string `gorm:"column:color;type:varchar(64);not null;comment:tab颜色" json:"color"` // tab颜色
78 | Status int32 `gorm:"column:status;type:tinyint;not null;default:1;comment:启用状态,1启用;2禁用" json:"status"` // 启用状态,1启用;2禁用
79 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
80 | UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
81 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间
82 | PromStrategies []*PromStrategy `gorm:"References:ID;foreignKey:ID;joinForeignKey:AlarmPageID;joinReferences:PromStrategyID;many2many:prom_strategy_alarm_pages" json:"prom_strategies"`
83 | Histories []*PromAlarmHistory `gorm:"References:ID;foreignKey:ID;joinForeignKey:AlarmPageID;joinReferences:HistoryID;many2many:prom_alarm_page_histories" json:"histories"`
84 | }
85 |
86 | type PromAlarmHistory struct {
87 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"`
88 | Node string `gorm:"column:node;type:varchar(64);not null;comment:node名称" json:"node"` // node名称
89 | Status string `gorm:"column:status;type:varchar(16);not null;comment:告警消息状态, 报警和恢复" json:"status"` // 告警消息状态, 报警和恢复
90 | Info string `gorm:"column:info;type:json;not null;comment:原始告警消息" json:"info"` // 原始告警消息
91 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
92 | StartAt int64 `gorm:"column:start_at;type:bigint unsigned;not null;comment:报警开始时间" json:"start_at"` // 报警开始时间
93 | EndAt int64 `gorm:"column:end_at;type:bigint unsigned;not null;comment:报警恢复时间" json:"end_at"` // 报警恢复时间
94 | Duration int64 `gorm:"column:duration;type:bigint unsigned;not null;comment:持续时间时间戳, 没有恢复, 时间戳是0" json:"duration"` // 持续时间时间戳, 没有恢复, 时间戳是0
95 | StrategyID int32 `gorm:"column:strategy_id;type:int unsigned;not null;index:idx__strategy_id,priority:1;comment:规则ID, 用于查询时候" json:"strategy_id"` // 规则ID, 用于查询时候
96 | LevelID int32 `gorm:"column:level_id;type:int unsigned;not null;index:idx__level_id,priority:1;comment:报警等级ID" json:"level_id"` // 报警等级ID
97 | Md5 string `gorm:"column:md5;type:char(32);not null;comment:md5" json:"md5"` // md5
98 | Pages []*PromAlarmPage `gorm:"References:ID;foreignKey:ID;joinForeignKey:AlarmPageID;joinReferences:PageID;many2many:prom_prom_alarm_page_histories" json:"pages"`
99 | }
100 |
101 | type Page struct {
102 | Curr int `json:"curr"`
103 | Size int `json:"size"`
104 | Total int64 `json:"total"`
105 | }
106 |
107 | func Test_getTag1(t *testing.T) {
108 | // ListStrategyResp ...
109 | type ListStrategyResp struct {
110 | List []*PromStrategy `json:"list"`
111 | Page Page `json:"page"`
112 | }
113 |
114 | fieldList := getTag(reflect.TypeOf(&ListStrategyResp{}))
115 | t.Log(fieldList)
116 | }
117 |
--------------------------------------------------------------------------------
/new.go:
--------------------------------------------------------------------------------
1 | package ginplus
2 |
3 | import (
4 | "context"
5 | "embed"
6 | "errors"
7 | "io/fs"
8 | "net/http"
9 | "os"
10 | "path"
11 | "reflect"
12 | "strings"
13 | "time"
14 |
15 | "github.com/aide-cloud/gin-plus/swagger"
16 | "github.com/gin-gonic/gin"
17 | "github.com/prometheus/client_golang/prometheus/promhttp"
18 | )
19 |
20 | var _ Server = (*GinEngine)(nil)
21 |
22 | type HandlerFunc func(controller any, t reflect.Method, req reflect.Type) gin.HandlerFunc
23 |
24 | type (
25 | GinEngine struct {
26 | *gin.Engine
27 |
28 | // 中间件
29 | middlewares []gin.HandlerFunc
30 | // 控制器
31 | controllers []any
32 | // 绑定前缀和http请求方法的映射
33 | httpMethodPrefixes map[string]httpMethod
34 | // 路由组基础路径
35 | basePath string
36 | // 自定义路由命名规则函数
37 | routeNamingRuleFunc func(methodName string) string
38 | // 自定义handler函数
39 | defaultHandler HandlerFunc
40 | // 自定义Response接口实现
41 | defaultResponse IResponse
42 | // 默认Bind函数
43 | defaultBind func(c *gin.Context, params any) error
44 |
45 | // 文档配置
46 | apiConfig ApiConfig
47 | defaultOpenApiYaml string
48 | apiRoutes map[string][]ApiRoute
49 |
50 | // 生成API路由开关, 默认为true
51 | genApiEnable bool
52 |
53 | // 内置启动地址
54 | // 默认为: :8080
55 | addr string
56 | server *http.Server
57 |
58 | // graphql配置
59 | graphqlConfig GraphqlConfig
60 |
61 | // prom metrics
62 | metrics *Metrics
63 | // ping
64 | ping *Ping
65 | }
66 |
67 | // Metrics prometheus metrics配置
68 | Metrics struct {
69 | Path string
70 | }
71 |
72 | // Ping ping配置
73 | Ping struct {
74 | HandlerFunc gin.HandlerFunc
75 | }
76 |
77 | // ApiConfig 文档配置,
78 | ApiConfig Info
79 |
80 | // RouteNamingRuleFunc 自定义路由命名函数
81 | RouteNamingRuleFunc func(methodName string) string
82 |
83 | // IMiddleware 中间件接口, 实现该接口的结构体将会被把中间件添加到该路由组的公共中间件中
84 | IMiddleware interface {
85 | Middlewares() []gin.HandlerFunc
86 | }
87 |
88 | // MethodeMiddleware 中间件接口, 为每个方法添加中间件
89 | MethodeMiddleware interface {
90 | MethodeMiddlewares() map[string][]gin.HandlerFunc
91 | }
92 |
93 | // Controller 控制器接口, 实现该接口的对象可以自定义模块的路由
94 | Controller interface {
95 | BasePath() string
96 | }
97 |
98 | // Route 路由参数结构
99 | Route struct {
100 | Path string
101 | HttpMethod string
102 | Handles []gin.HandlerFunc
103 | }
104 |
105 | // ApiRoute api路由参数结构, 用于生成文档
106 | ApiRoute struct {
107 | Path string
108 | HttpMethod string
109 | MethodName string
110 | ReqParams Field
111 | RespParams Field
112 | }
113 |
114 | // OptionFun GinEngine配置函数
115 | OptionFun func(*GinEngine)
116 |
117 | // httpMethod http请求方法
118 | httpMethod struct {
119 | key string
120 | }
121 | // HttpMethod http请求方法, 绑定前缀和http请求方法的映射
122 | HttpMethod struct {
123 | Prefix string
124 | Method httpMethod
125 | }
126 |
127 | // GraphqlConfig graphql配置
128 | GraphqlConfig struct {
129 | // Enable 是否启用
130 | Enable bool
131 | // HandlePath graphql请求路径
132 | HandlePath string
133 | // SchemaPath graphql schema文件路径
134 | ViewPath string
135 | // Root graphql 服务根节点
136 | Root any
137 | // Content graphql schema文件内容
138 | Content embed.FS
139 | }
140 | )
141 |
142 | const (
143 | get = "Get"
144 | post = "Post"
145 | put = "Put"
146 | del = "Delete"
147 | patch = "Patch"
148 | head = "Head"
149 | option = "Option"
150 | )
151 |
152 | const (
153 | defaultTitle = "github.com/aide-cloud/gin-plus"
154 | defaultVersion = "v0.5.0"
155 | defaultMetricsPath = "/metrics"
156 | defaultPingPath = "/ping"
157 | )
158 |
159 | var (
160 | Get = httpMethod{key: get}
161 | Post = httpMethod{key: post}
162 | Put = httpMethod{key: put}
163 | Delete = httpMethod{key: del}
164 | Patch = httpMethod{key: patch}
165 | Head = httpMethod{key: head}
166 | Option = httpMethod{key: option}
167 | )
168 |
169 | // defaultPrefixes is the default prefixes.
170 | var defaultPrefixes = map[string]httpMethod{
171 | get: Get,
172 | post: Post,
173 | put: Put,
174 | del: Delete,
175 | patch: Patch,
176 | head: Head,
177 | option: Option,
178 | }
179 |
180 | // New 返回一个GinEngine实例
181 | func New(r *gin.Engine, opts ...OptionFun) *GinEngine {
182 | instance := &GinEngine{
183 | Engine: r,
184 | httpMethodPrefixes: defaultPrefixes,
185 | defaultOpenApiYaml: defaultOpenApiYaml,
186 | defaultResponse: NewResponse(),
187 | defaultBind: Bind,
188 | routeNamingRuleFunc: routeToCamel,
189 | apiRoutes: make(map[string][]ApiRoute),
190 | genApiEnable: true,
191 | apiConfig: ApiConfig{
192 | Title: defaultTitle,
193 | Version: defaultVersion,
194 | },
195 | ping: &Ping{HandlerFunc: func(ctx *gin.Context) {
196 | ctx.Status(http.StatusOK)
197 | }},
198 | metrics: &Metrics{Path: defaultMetricsPath},
199 | addr: ":8080",
200 | }
201 | for _, opt := range opts {
202 | opt(instance)
203 | }
204 |
205 | if instance.defaultHandler == nil {
206 | instance.defaultHandler = instance.newDefaultHandler
207 | }
208 |
209 | instance.Use(instance.middlewares...)
210 |
211 | for _, c := range instance.controllers {
212 | instance.genRoute(nil, c, false)
213 | }
214 |
215 | return instance
216 | }
217 |
218 | // Start 启动HTTP服务器
219 | func (l *GinEngine) Start() error {
220 | if l.server == nil {
221 | //创建HTTP服务器
222 | server := &http.Server{
223 | Addr: l.addr,
224 | Handler: l.Engine,
225 | }
226 | l.server = server
227 | }
228 |
229 | //启动HTTP服务器
230 | go func() {
231 | if err := l.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
232 | Logger().Sugar().Infof("[GIN-PLUS] [INFO] Server listen: %s\n", err)
233 | }
234 | }()
235 |
236 | Logger().Sugar().Infof("[GIN-PLUS] [INFO] Server is running at %s", l.addr)
237 |
238 | return nil
239 | }
240 |
241 | // Stop 停止HTTP服务器
242 | func (l *GinEngine) Stop() {
243 | //创建超时上下文,Shutdown可以让未处理的连接在这个时间内关闭
244 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
245 | defer cancel()
246 |
247 | //停止HTTP服务器
248 | if err := l.server.Shutdown(ctx); err != nil {
249 | Logger().Sugar().Errorf("[GIN-PLUS] [INFO] Server Shutdown: %v", err)
250 | }
251 |
252 | Logger().Sugar().Infof("[GIN-PLUS] [INFO] Server stopped")
253 | }
254 |
255 | func registerPing(instance *GinEngine, ping *Ping) {
256 | if ping != nil {
257 | instance.GET(defaultPingPath, ping.HandlerFunc)
258 | return
259 | }
260 | }
261 |
262 | func registerMetrics(instance *GinEngine, metrics *Metrics) {
263 | if metrics == nil {
264 | return
265 | }
266 | if metrics.Path == "" {
267 | metrics.Path = defaultMetricsPath
268 | }
269 | instance.GET(metrics.Path, gin.WrapH(promhttp.Handler()))
270 | }
271 |
272 | func registerSwaggerUI(instance *GinEngine, enable bool) {
273 | if !enable {
274 | return
275 | }
276 | instance.genOpenApiYaml()
277 | fp, _ := fs.Sub(swagger.Dist, "dist")
278 | instance.StaticFS("/swagger-ui", http.FS(fp))
279 | instance.GET("/openapi/doc/swagger", func(ctx *gin.Context) {
280 | file, _ := os.ReadFile(instance.defaultOpenApiYaml)
281 | ctx.Writer.Header().Set("Content-Type", "text/yaml; charset=utf-8")
282 | _, _ = ctx.Writer.Write(file)
283 | })
284 | }
285 |
286 | func registerGraphql(instance *GinEngine, config GraphqlConfig) {
287 | if !config.Enable || config.Root == nil {
288 | return
289 | }
290 | if config.HandlePath == "" {
291 | config.HandlePath = DefaultHandlePath
292 | }
293 | if config.ViewPath == "" {
294 | config.ViewPath = DefaultViewPath
295 | }
296 | instance.POST(config.HandlePath, gin.WrapH(Handler(config.Root, config.Content)))
297 | instance.GET(config.ViewPath, gin.WrapF(View(config.HandlePath)))
298 | }
299 |
300 | func (l *GinEngine) RegisterPing(ping ...*Ping) *GinEngine {
301 | if len(ping) > 0 {
302 | l.ping = ping[0]
303 | }
304 | registerPing(l, l.ping)
305 | return l
306 | }
307 |
308 | // RegisterMetrics 注册prometheus metrics
309 | func (l *GinEngine) RegisterMetrics(metrics ...*Metrics) *GinEngine {
310 | if len(metrics) > 0 {
311 | l.metrics = metrics[0]
312 | }
313 | registerMetrics(l, l.metrics)
314 | return l
315 | }
316 |
317 | // RegisterSwaggerUI 注册swagger ui
318 | func (l *GinEngine) RegisterSwaggerUI() *GinEngine {
319 | registerSwaggerUI(l, l.genApiEnable)
320 | return l
321 | }
322 |
323 | // RegisterGraphql 注册graphql
324 | func (l *GinEngine) RegisterGraphql(config ...*GraphqlConfig) *GinEngine {
325 | if len(config) > 0 {
326 | l.graphqlConfig = *config[0]
327 | }
328 | registerGraphql(l, l.graphqlConfig)
329 | return l
330 | }
331 |
332 | // WithControllers sets the controllers.
333 | func WithControllers(controllers ...any) OptionFun {
334 | return func(g *GinEngine) {
335 | g.controllers = controllers
336 | }
337 | }
338 |
339 | // WithMiddlewares sets the middlewares.
340 | func WithMiddlewares(middlewares ...gin.HandlerFunc) OptionFun {
341 | return func(g *GinEngine) {
342 | g.middlewares = middlewares
343 | }
344 | }
345 |
346 | // WithHttpMethodPrefixes sets the prefixes.
347 | func WithHttpMethodPrefixes(prefixes ...HttpMethod) OptionFun {
348 | return func(g *GinEngine) {
349 | prefixHttpMethodMap := make(map[string]httpMethod)
350 | for _, prefix := range prefixes {
351 | if prefix.Prefix == "" || prefix.Method.key == "" {
352 | continue
353 | }
354 | prefixHttpMethodMap[prefix.Prefix] = prefix.Method
355 | }
356 | g.httpMethodPrefixes = prefixHttpMethodMap
357 | }
358 | }
359 |
360 | // AppendHttpMethodPrefixes append the prefixes.
361 | func AppendHttpMethodPrefixes(prefixes ...HttpMethod) OptionFun {
362 | return func(g *GinEngine) {
363 | prefixHttpMethodMap := g.httpMethodPrefixes
364 | if prefixHttpMethodMap == nil {
365 | prefixHttpMethodMap = make(map[string]httpMethod)
366 | }
367 | for _, prefix := range prefixes {
368 | if prefix.Prefix == "" || prefix.Method.key == "" {
369 | continue
370 | }
371 | prefixHttpMethodMap[prefix.Prefix] = prefix.Method
372 | }
373 | g.httpMethodPrefixes = prefixHttpMethodMap
374 | }
375 | }
376 |
377 | // WithBasePath sets the base path.
378 | func WithBasePath(basePath string) OptionFun {
379 | return func(g *GinEngine) {
380 | g.basePath = path.Join("/", basePath)
381 | }
382 | }
383 |
384 | // WithRouteNamingRuleFunc 自定义路由命名函数
385 | func WithRouteNamingRuleFunc(ruleFunc RouteNamingRuleFunc) OptionFun {
386 | return func(g *GinEngine) {
387 | g.routeNamingRuleFunc = ruleFunc
388 | }
389 | }
390 |
391 | // WithApiConfig sets the title.
392 | func WithApiConfig(c ApiConfig) OptionFun {
393 | return func(g *GinEngine) {
394 | g.apiConfig = c
395 | }
396 | }
397 |
398 | // WithOpenApiYaml 自定义api文件存储位置和文件名称
399 | func WithOpenApiYaml(dir, filename string) OptionFun {
400 | return func(g *GinEngine) {
401 | if !strings.HasSuffix(filename, ".yaml") {
402 | Logger().Sugar().Infof("[GIN-PLUS] [WARNING] filename has no (.yaml) suffix, so the default (%s) is used as the filename.\n", defaultOpenApiYaml)
403 | }
404 | g.defaultOpenApiYaml = path.Join(dir, filename)
405 | }
406 | }
407 |
408 | // WithGenApiEnable 设置是否生成API路由
409 | func WithGenApiEnable(enable bool) OptionFun {
410 | return func(g *GinEngine) {
411 | g.genApiEnable = enable
412 | }
413 | }
414 |
415 | // WithAddr 设置启动地址
416 | func WithAddr(addr string) OptionFun {
417 | return func(g *GinEngine) {
418 | g.addr = addr
419 | }
420 | }
421 |
422 | // WithHttpServer 设置启动地址
423 | func WithHttpServer(server *http.Server) OptionFun {
424 | return func(g *GinEngine) {
425 | server.Handler = g.Engine
426 | if server.Addr == "" {
427 | server.Addr = g.addr
428 | }
429 | g.server = server
430 | }
431 | }
432 |
433 | // WithGraphqlConfig 设置graphql配置
434 | func WithGraphqlConfig(config GraphqlConfig) OptionFun {
435 | return func(g *GinEngine) {
436 | g.graphqlConfig = config
437 | }
438 | }
439 |
440 | // WithDefaultHandler 自定义handler函数
441 | func WithDefaultHandler(handler HandlerFunc) OptionFun {
442 | return func(g *GinEngine) {
443 | g.defaultHandler = handler
444 | }
445 | }
446 |
447 | // WithDefaultResponse 自定义Response接口实现
448 | func WithDefaultResponse(response IResponse) OptionFun {
449 | return func(g *GinEngine) {
450 | g.defaultResponse = response
451 | }
452 | }
453 |
454 | // WithMetrics 自定义Metrics
455 | func WithMetrics(metrics *Metrics) OptionFun {
456 | return func(g *GinEngine) {
457 | g.metrics = metrics
458 | }
459 | }
460 |
461 | // WithPing 自定义Ping
462 | func WithPing(ping *Ping) OptionFun {
463 | return func(g *GinEngine) {
464 | g.ping = ping
465 | }
466 | }
467 |
468 | // WithBind 自定义Bind
469 | func WithBind(bind func(c *gin.Context, params any) error) OptionFun {
470 | return func(g *GinEngine) {
471 | g.defaultBind = bind
472 | }
473 | }
474 |
--------------------------------------------------------------------------------
/example/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
41 | github.com/aide-cloud/gin-plus v0.1.7 h1:LzXAQBcM8pDPz+4vpFjiZaxPtgQ/GEGG21iFf7QWhZg=
42 | github.com/aide-cloud/gin-plus v0.1.7/go.mod h1:6gpcyVxTZZrMve7BSxZAoSiy5keINVGbFBAhZdUZIEU=
43 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
44 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
45 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
46 | github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
47 | github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk=
48 | github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
49 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
50 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
51 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
52 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
53 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
54 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
55 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
56 | github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
57 | github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
58 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
59 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
60 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
61 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
62 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
63 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
64 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
65 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
66 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
67 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
68 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
69 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
70 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
71 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
72 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
73 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
74 | github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
75 | github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
76 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
77 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
78 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
79 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
80 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
81 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
82 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
83 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
84 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
85 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
86 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
87 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
88 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
89 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
90 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
91 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
92 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
93 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
94 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
95 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
96 | github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo=
97 | github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
98 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
99 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
100 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
101 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
102 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
103 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
104 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
105 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
106 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
107 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
108 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
109 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
110 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
111 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
112 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
113 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
114 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
115 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
116 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
117 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
118 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
119 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
120 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
121 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
122 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
123 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
124 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
125 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
126 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
127 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
128 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
129 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
130 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
131 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
132 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
133 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
134 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
135 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
136 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
137 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
138 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
139 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
140 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
141 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
142 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
143 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
144 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
145 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
146 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
147 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
148 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
149 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
150 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
151 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
152 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
153 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
154 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
155 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
156 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
157 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
158 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
159 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
160 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
161 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
162 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
163 | github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc=
164 | github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
165 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
166 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
167 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
168 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
169 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
170 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
171 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
172 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
173 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
174 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
175 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
176 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
177 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
178 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
179 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
180 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
181 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
182 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
183 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
184 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
185 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
186 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
187 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
188 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
189 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
190 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
191 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
192 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
193 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
194 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
195 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
196 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
197 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
198 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
199 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
200 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
201 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
202 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
203 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
204 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
205 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
206 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
207 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
208 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
209 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
210 | github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
211 | github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
212 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
213 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
214 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
215 | github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
216 | github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
217 | github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
218 | github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
219 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
220 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
221 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
222 | github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
223 | github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
224 | github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
225 | github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
226 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
227 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
228 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
229 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
230 | github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
231 | github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
232 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
233 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
234 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
235 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
236 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
237 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
238 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
239 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
240 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
241 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
242 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
243 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
244 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
245 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
246 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
247 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
248 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
249 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
250 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
251 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
252 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
253 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
254 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
255 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
256 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
257 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
258 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
259 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
260 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
261 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
262 | go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
263 | go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
264 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
265 | golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
266 | golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
267 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
268 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
269 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
270 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
271 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
272 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
273 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
274 | golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
275 | golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
276 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
277 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
278 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
279 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
280 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
281 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
282 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
283 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
284 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
285 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
286 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
287 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
288 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
289 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
290 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
291 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
292 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
293 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
294 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
295 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
296 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
297 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
298 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
299 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
300 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
301 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
302 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
303 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
304 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
305 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
306 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
307 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
308 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
309 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
310 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
311 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
312 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
313 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
314 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
315 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
316 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
317 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
318 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
319 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
320 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
321 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
322 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
323 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
324 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
325 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
326 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
327 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
328 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
329 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
330 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
331 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
332 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
333 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
334 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
335 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
336 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
337 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
338 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
339 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
340 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
341 | golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
342 | golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
343 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
344 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
345 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
346 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
347 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
348 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
349 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
350 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
351 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
352 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
353 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
354 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
355 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
356 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
357 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
358 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
359 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
360 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
361 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
362 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
363 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
364 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
365 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
366 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
367 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
368 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
369 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
370 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
371 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
372 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
373 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
374 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
375 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
376 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
377 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
378 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
379 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
380 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
381 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
382 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
383 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
384 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
385 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
386 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
387 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
388 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
389 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
390 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
391 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
392 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
393 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
394 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
395 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
396 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
397 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
398 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
399 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
400 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
401 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
402 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
403 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
404 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
405 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
406 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
407 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
408 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
409 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
410 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
411 | golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
412 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
413 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
414 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
415 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
416 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
417 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
418 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
419 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
420 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
421 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
422 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
423 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
424 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
425 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
426 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
427 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
428 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
429 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
430 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
431 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
432 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
433 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
434 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
435 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
436 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
437 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
438 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
439 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
440 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
441 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
442 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
443 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
444 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
445 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
446 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
447 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
448 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
449 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
450 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
451 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
452 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
453 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
454 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
455 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
456 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
457 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
458 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
459 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
460 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
461 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
462 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
463 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
464 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
465 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
466 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
467 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
468 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
469 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
470 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
471 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
472 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
473 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
474 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
475 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
476 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
477 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
478 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
479 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
480 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
481 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
482 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
483 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
484 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
485 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
486 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
487 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
488 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
489 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
490 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
491 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
492 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
493 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
494 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
495 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
496 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
497 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
498 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
499 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
500 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
501 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
502 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
503 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
504 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
505 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
506 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
507 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
508 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
509 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
510 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
511 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
512 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
513 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
514 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
515 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
516 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
517 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
518 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
519 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
520 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
521 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
522 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
523 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
524 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
525 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
526 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
527 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
528 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
529 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
530 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
531 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
532 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
533 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
534 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
535 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
536 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
537 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
538 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
539 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
540 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
541 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
542 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
543 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
544 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
545 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
546 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
547 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
548 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
549 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
550 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
551 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
552 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
553 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
554 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
555 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
556 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
557 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
558 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
559 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
560 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
561 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
562 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
563 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
564 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
565 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
566 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
567 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
568 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
569 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
570 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
571 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
572 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
573 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
574 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
575 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
576 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
577 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
578 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
579 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
580 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
581 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
582 |
--------------------------------------------------------------------------------