diff --git a/.travis.yml b/.travis.yml index eb83d1d9d..61660acb3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.11.x + - 1.12.x # Only clone the most recent commit. git: diff --git a/README.md b/README.md index dd8bd63ef..fa1269974 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ![kratos](doc/img/kratos3.png) [![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/) +[![Build Status](https://travis-ci.org/bilibili/kratos.svg?branch=master)](https://travis-ci.org/bilibili/kratos) [![GoDoc](https://godoc.org/github.com/bilibili/kratos?status.svg)](https://godoc.org/github.com/bilibili/kratos) [![Go Report Card](https://goreportcard.com/badge/github.com/bilibili/kratos)](https://goreportcard.com/report/github.com/bilibili/kratos) @@ -66,3 +67,4 @@ Kratos is under the MIT license. See the [LICENSE](./LICENSE) file for details. ------------- *Please report bugs, concerns, suggestions by issues, or join QQ-group 716486124 to discuss problems around source code.* + diff --git a/doc/wiki-cn/kratos-tool.md b/doc/wiki-cn/kratos-tool.md index cb3562801..3253d6d5f 100644 --- a/doc/wiki-cn/kratos-tool.md +++ b/doc/wiki-cn/kratos-tool.md @@ -55,13 +55,13 @@ kratos new kratos-demo kratos new kratos-demo -o YourName -d YourPath ``` -注意,`kratos new`默认是不会生成`grpc`示例代码的,如需生成请加`--grpc`,如下: +注意,`kratos new`默认是不会生成通过protobuf定义的`grpc`和`bm`示例代码的,如需生成请加`--proto`,如下: ```shell -kratos new kratos-demo -o YourName -d YourPath --grpc +kratos new kratos-demo -o YourName -d YourPath --proto ``` -特别注意,如果不是macos系统,生成的示例项目`api`目录下的`proto`文件并不会自动生成对应的`.pb.go`文件,需要参考以下说明进行生成。 +特别注意,如果不是macos系统,生成的示例项目`api`目录下的`proto`文件并不会自动生成对应的`.pb.go`和`.bm.go`文件,需要参考以下说明进行生成。 [protoc说明](protoc.md) @@ -74,8 +74,9 @@ kratos new kratos-demo -o YourName -d YourPath --grpc `kratos tool`是基于proto生成http&grpc代码,生成缓存回源代码,生成memcache执行代码,生成swagger文档等工具集,先看下`kratos tool`的执行效果: ``` +swagger(已安装): swagger api文档 Author(goswagger.io) [2019/05/05] +protoc(已安装): 快速方便生成pb.go和bm.go的protoc封装,windows、Linux请先安装protoc工具 Author(kratos) [2019/05/04] kratos(已安装): Kratos工具集本体 Author(kratos) [2019/04/02] -kprotoc(已安装): 快速方便生成pb.go的protoc封装,不支持Windows,Linux请先安装protoc工具 Author(kratos) [2019/04/02] 安装工具: kratos tool install demo 执行工具: kratos tool demo @@ -87,25 +88,49 @@ kprotoc(已安装): 快速方便生成pb.go的protoc封装,不支持Windows, ***小小说明:如未安装工具,第一次运行也可自动安装,不需要特别执行install*** 目前已经集成的工具有: -* `kprotoc`用于快速生成`pb.go`文件,但目前不支持windows,Linux也需要先自己安装`protoc`工具。 +* `protoc`用于快速生成`*.pb.go`和`*.bm.go`文件,但目前不支持windows,Linux也需要先自己安装`protoc`工具。 +* `swagger`用于显示自动生成的BM API接口文档,通过 `kratos tool swagger serve api/api.swagger.json` 可以访问到文档。 * TODOs... -### kratos tool kprotoc +### kratos tool protoc -该命令运行没其他参数,直接`kratos tool kprotoc`运行即可。但使用前需特别说明: +该命令运行没其他参数,直接`kratos tool protoc`运行即可。但使用前需特别说明: -* 该工具不支持Windows用户,请安装`protoc`和`gogo protobuf`工具 -* 该工具在Linux下运行,需提前安装好`protoc`工具 +* 该工具在Windows/Linux下运行,需提前安装好`protoc`工具 -该工具实际是一段`shell`脚本,其中自动将`protoc`命令进行了拼接,识别了需要`include`的目录和当前目录下的`proto`文件,最终会拼接为如下命令进行执行: +该工具实际是一段`shell`脚本,其中自动将`protoc`命令进行了拼接,识别了需要`*.proto`的目录和当前目录下的`proto`文件,最终会拼接为如下命令进行执行: ```shell -protoc -I/Users/felix/work/go/src:/usr/local/include --gogofast_out=plugins=grpc:/Users/felix/work/go/src /Users/felix/work/go/src/kratos-demo/api/api.proto +export $KRATOS_HOME = kratos路径 +export $KRATOS_DEMO = 项目路径 + +// 生成:api.pb.go +protoc -I$GOPATH/src:$KRATOS_HOME/tool/protobuf/pkg/extensions:$KRATOS_DEMO/api --gogofast_out=plugins=grpc:$KRATOS_DEMO/api $KRATOS_DEMO/api/api.proto +// 生成:api.bm.go +protoc -I$GOPATH/src:$KRATOS_HOME/tool/protobuf/pkg/extensions:$KRATOS_DEMO/api --bm_out=$KRATOS_DEMO/api $KRATOS_DEMO/api/api.proto +// 生成:api.swagger.json +protoc -I$GOPATH/src:$KRATOS_HOME/tool/protobuf/pkg/extensions:$KRATOS_DEMO/api --bswagger_out=$KRATOS_DEMO/api $KRATOS_DEMO/api/api.proto ``` -Windows和Linux用户可以参考该命令进行`proto`生成`pb.go`文件,也可以参考[protoc说明](protoc.md)。 +大家也可以参考该命令进行`proto`生成`*.pb.go`和`*.bm.go`文件,也可以参考[protoc说明](protoc.md)。 -### TODOs +### Tool examples +```shell +// new a project +cd $GOPATH/src +kratos new kratos-demo -o Tinker --proto + +// build & run +cd kratos-demo +kratos run + +// swagger docs +kratos tool swagger serve kratos-demo/api/api.swagger.json + +// generate proto +cd kratos-demo/api +kratos tool protoc api.proto +``` ------------- diff --git a/go.mod b/go.mod index ca7258b29..10cd7c967 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/prometheus/client_golang v0.9.2 github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 // indirect github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec // indirect + github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 github.com/sirupsen/logrus v1.4.1 // indirect github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a github.com/stretchr/testify v1.3.0 @@ -29,6 +30,7 @@ require ( github.com/urfave/cli v1.20.0 golang.org/x/net v0.0.0-20190311183353-d8887717615a golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect + google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 google.golang.org/grpc v1.20.1 gopkg.in/AlecAivazis/survey.v1 v1.8.2 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect diff --git a/tool/kprotoc/install_kprotoc.sh b/tool/kprotoc/install_kprotoc.sh deleted file mode 100755 index 80282888e..000000000 --- a/tool/kprotoc/install_kprotoc.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -set -e -if [[ -z $GOPATH ]]; then - GOPATH=${HOME}/go -fi -BIN_PATH=$( cut -d ':' -f 1 <<< "$GOPATH" )/bin -if [[ ! -z $GOBIN ]]; then - BIN_PATH=$GOBIN -fi -if [[ ! -z $INSTALL_PATH ]]; then - BIN_PATH=$INSTALL_PATH -fi -if [[ -f $BIN_PATH/kprotoc ]]; then - echo "kprotoc alreay install, remove $BIN_PATH/kprotoc first to reinstall." - exit 1; -fi - -ln -s $GOPATH/src/github.com/bilibili/kratos/tool/kprotoc/kprotoc.sh $BIN_PATH/kprotoc -echo "install kprotoc to $BIN_PATH/kprotoc done!" diff --git a/tool/kprotoc/kprotoc.sh b/tool/kprotoc/kprotoc.sh deleted file mode 100755 index fac1fd530..000000000 --- a/tool/kprotoc/kprotoc.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/bash -DEFAULT_PROTOC_GEN="gogofast" -DEFAULT_PROTOC="protoc" -KRATOS_DIR_NAME="github.com/bilibili/kratos" -USR_INCLUDE_DIR="/usr/local/include" -GOPATH=$GOPATH -if [[ -z $GOPATH ]]; then - GOPATH=${HOME}/go -fi - -function _install_protoc() { - osname=$(uname -s) - echo "install protoc ..." - case $osname in - "Darwin" ) - brew install protobuf - ;; - *) - echo "unknown operating system, need install protobuf manual see: https://developers.google.com/protocol-buffers" - exit 1 - ;; - esac -} - -function _install_protoc_gen() { - local protoc_gen=$1 - case $protoc_gen in - "gofast" ) - echo "install gofast from github.com/gogo/protobuf/protoc-gen-gofast" - go get github.com/gogo/protobuf/protoc-gen-gofast - ;; - "gogofast" ) - echo "install gogofast from github.com/gogo/protobuf/protoc-gen-gogofast" - go get github.com/gogo/protobuf/protoc-gen-gogofast - ;; - "gogo" ) - echo "install gogo from github.com/gogo/protobuf/protoc-gen-gogo" - go get github.com/gogo/protobuf/protoc-gen-gogo - ;; - "go" ) - echo "install protoc-gen-go from github.com/golang/protobuf" - go get github.com/golang/protobuf/{proto,protoc-gen-go} - ;; - *) - echo "can't install protoc-gen-${protoc_gen} automatic !" - exit 1; - ;; - esac -} - -function _find_kratos_dir() { - local kratos_dir_name=$1 - local current_dir="$GOPATH/src/$kratos_dir_name" - if [[ ! -d $current_dir ]]; then - go get -u $kratos_dir_name - fi - echo $current_dir -} - -function _esc_string() { - echo $(echo "$1" | sed 's_/_\\/_g') -} - -function _run_protoc() { - local proto_dir=$1 - local proto_files=$(find $proto_dir -maxdepth 1 -name "*.proto") - if [[ -z $proto_files ]]; then - return - fi - local protoc_cmd="$PROTOC -I$PROTO_PATH --${PROTOC_GEN}_out=plugins=grpc:${GOPATH}/src ${proto_files}" - echo $protoc_cmd - $protoc_cmd -} - -if [[ -z $PROTOC ]]; then - PROTOC=${DEFAULT_PROTOC} - which $PROTOC - if [[ "$?" -ne "0" ]]; then - _install_protoc - fi -fi -if [[ -z $PROTOC_GEN ]]; then - PROTOC_GEN=${DEFAULT_PROTOC_GEN} - which protoc-gen-$PROTOC_GEN - if [[ "$?" -ne "0" ]]; then - _install_protoc_gen $PROTOC_GEN - fi -fi - -KRATOS_DIR=$(_find_kratos_dir $KRATOS_DIR_NAME) -if [[ "$?" != "0" ]]; then - echo "can't find kratos directoy" - exit 1 -fi - -KRATOS_PARENT=$(dirname $KRATOS_DIR) - -if [[ -z $PROTO_PATH ]]; then - PROTO_PATH=$GOPATH/src:$KRATOS_PARENT:$USR_INCLUDE_DIR -else - PROTO_PATH=$GOPATH/src:$PROTO_PATH:$KRATOS_PARENT:$USR_INCLUDE_DIR -fi - -if [[ ! -z $1 ]]; then - cd $1 -fi -TARGET_DIR=$(pwd) - -# switch to $GOPATH/src -cd $GOPATH/src -echo "switch workdir to $GOPATH/src" - -DIRS=$(find $TARGET_DIR -type d) - -for dir in $DIRS; do - echo "run protoc in $dir" - _run_protoc $dir -done diff --git a/tool/kratos-protoc/bm.go b/tool/kratos-protoc/bm.go new file mode 100644 index 000000000..b48105955 --- /dev/null +++ b/tool/kratos-protoc/bm.go @@ -0,0 +1,25 @@ +package main + +import ( + "os/exec" + + "github.com/urfave/cli" +) + +const ( + _getBMGen = "go get github.com/bilibili/kratos/tool/protobuf/protoc-gen-bm" + _bmProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bm_out=explicit_http=true:." +) + +func installBMGen() error { + if _, err := exec.LookPath("protoc-gen-bm"); err != nil { + if err := goget(_getBMGen); err != nil { + return err + } + } + return nil +} + +func genBM(ctx *cli.Context) error { + return generate(ctx, _bmProtoc) +} diff --git a/tool/kratos-protoc/grpc.go b/tool/kratos-protoc/grpc.go new file mode 100644 index 000000000..ae0904808 --- /dev/null +++ b/tool/kratos-protoc/grpc.go @@ -0,0 +1,25 @@ +package main + +import ( + "os/exec" + + "github.com/urfave/cli" +) + +const ( + _getGRPCGen = "go get github.com/gogo/protobuf/protoc-gen-gogofast" + _grpcProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --gogofast_out=plugins=grpc:." +) + +func installGRPCGen() error { + if _, err := exec.LookPath("protoc-gen-gogofast"); err != nil { + if err := goget(_getGRPCGen); err != nil { + return err + } + } + return nil +} + +func genGRPC(ctx *cli.Context) error { + return generate(ctx, _grpcProtoc) +} diff --git a/tool/kratos-protoc/main.go b/tool/kratos-protoc/main.go new file mode 100644 index 000000000..3b379f726 --- /dev/null +++ b/tool/kratos-protoc/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "protc" + app.Usage = "protobuf生成工具" + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "bm", + Usage: "whether to use BM for generation", + Destination: &withBM, + }, + cli.BoolFlag{ + Name: "grpc", + Usage: "whether to use gRPC for generation", + Destination: &withGRPC, + }, + cli.BoolFlag{ + Name: "swagger", + Usage: "whether to use swagger for generation", + Destination: &withSwagger, + }, + } + app.Action = func(c *cli.Context) error { + return protocAction(c) + } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/tool/kratos-protoc/protoc.go b/tool/kratos-protoc/protoc.go new file mode 100644 index 000000000..4943f733d --- /dev/null +++ b/tool/kratos-protoc/protoc.go @@ -0,0 +1,160 @@ +package main + +import ( + "errors" + "fmt" + "go/build" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" + + "github.com/urfave/cli" +) + +var ( + withBM bool + withGRPC bool + withSwagger bool +) + +func protocAction(ctx *cli.Context) (err error) { + if err = checkProtoc(); err != nil { + return err + } + if !withGRPC && !withBM && !withSwagger { + withBM = true + withGRPC = true + withSwagger = true + } + if withBM { + if err = installBMGen(); err != nil { + return + } + if err = genBM(ctx); err != nil { + return + } + } + if withGRPC { + if err = installGRPCGen(); err != nil { + return err + } + if err = genGRPC(ctx); err != nil { + return + } + } + if withSwagger { + if err = installSwaggerGen(); err != nil { + return + } + if err = genSwagger(ctx); err != nil { + return + } + } + log.Printf("generate %v success.\n", ctx.Args()) + return nil +} + +func checkProtoc() error { + if _, err := exec.LookPath("protoc"); err != nil { + switch runtime.GOOS { + case "darwin": + fmt.Println("brew install protobuf") + cmd := exec.Command("brew", "install", "protobuf") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err = cmd.Run(); err != nil { + return err + } + case "linux": + fmt.Println("snap install --classic protobuf") + cmd := exec.Command("snap", "install", "--classic", "protobuf") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err = cmd.Run(); err != nil { + return err + } + default: + return errors.New("您还没安装protobuf,请进行手动安装:https://github.com/protocolbuffers/protobuf/releases") + } + } + return nil +} + +func generate(ctx *cli.Context, protoc string) error { + pwd, _ := os.Getwd() + gosrc := path.Join(gopath(), "src") + ext, err := latestKratos() + if err != nil { + return err + } + line := fmt.Sprintf(protoc, gosrc, ext, pwd) + log.Println(line, strings.Join(ctx.Args(), " ")) + args := strings.Split(line, " ") + args = append(args, ctx.Args()...) + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = pwd + cmd.Env = os.Environ() + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +func goget(url string) error { + args := strings.Split(url, " ") + cmd := exec.Command(args[0], args[1:]...) + cmd.Env = os.Environ() + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + log.Println(url) + return cmd.Run() +} + +func latestKratos() (string, error) { + gopath := gopath() + ext := path.Join(gopath, "src/github.com/bilibili/kratos/tool/protobuf/pkg/extensions") + if _, err := os.Stat(ext); !os.IsNotExist(err) { + return ext, nil + } + ext = path.Join(gopath, "pkg/mod/github.com/bilibili") + files, err := ioutil.ReadDir(ext) + if err != nil { + return "", err + } + if len(files) == 0 { + return "", errors.New("not found kratos package") + } + return path.Join(ext, files[len(files)-1].Name(), "tool/protobuf/pkg/extensions"), nil +} + +func gopath() (gp string) { + gopaths := strings.Split(os.Getenv("GOPATH"), ":") + if len(gopaths) == 1 { + return gopaths[0] + } + pwd, err := os.Getwd() + if err != nil { + return + } + abspwd, err := filepath.Abs(pwd) + if err != nil { + return + } + for _, gopath := range gopaths { + absgp, err := filepath.Abs(gopath) + if err != nil { + return + } + if strings.HasPrefix(abspwd, absgp) { + return absgp + } + } + return build.Default.GOPATH +} diff --git a/tool/kratos-protoc/swagger.go b/tool/kratos-protoc/swagger.go new file mode 100644 index 000000000..7f87c5618 --- /dev/null +++ b/tool/kratos-protoc/swagger.go @@ -0,0 +1,25 @@ +package main + +import ( + "os/exec" + + "github.com/urfave/cli" +) + +const ( + _getSwaggerGen = "go get github.com/bilibili/kratos/tool/protobuf/protoc-gen-bswagger" + _swaggerProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bswagger_out=explicit_http=true:." +) + +func installSwaggerGen() error { + if _, err := exec.LookPath("protoc-gen-bswagger"); err != nil { + if err := goget(_getSwaggerGen); err != nil { + return err + } + } + return nil +} + +func genSwagger(ctx *cli.Context) error { + return generate(ctx, _swaggerProtoc) +} diff --git a/tool/kratos/main.go b/tool/kratos/main.go index 0ebe3453d..778e02496 100644 --- a/tool/kratos/main.go +++ b/tool/kratos/main.go @@ -31,8 +31,8 @@ func main() { Destination: &p.Path, }, cli.BoolFlag{ - Name: "grpc", - Usage: "whether to use grpc for create project", + Name: "proto", + Usage: "whether to use protobuf for create project", Destination: &p.WithGRPC, }, }, @@ -50,12 +50,12 @@ func main() { Usage: "kratos run", Action: runAction, }, - { - Name: "tool", - Aliases: []string{"t"}, - Usage: "kratos tool", - Action: toolAction, + Name: "tool", + Aliases: []string{"t"}, + Usage: "kratos tool", + Action: toolAction, + SkipFlagParsing: true, }, { Name: "version", diff --git a/tool/kratos/project.go b/tool/kratos/project.go index 0de61dc40..ac022a3ee 100644 --- a/tool/kratos/project.go +++ b/tool/kratos/project.go @@ -2,11 +2,9 @@ package main import ( "bytes" - "fmt" "io/ioutil" "os" "os/exec" - "runtime" "strings" "text/template" ) @@ -115,16 +113,14 @@ func create() (err error) { if err = genpb(); err != nil { return } - if runtime.GOOS != "darwin" { - fmt.Println("您的操作系统不是macos,kprotoc工具无法正常运行,请参看kratos tool文档!") - fmt.Println("地址:", toolDoc) - } } return } func genpb() error { cmd := exec.Command("go", "generate", p.Path+"/api/generate.go") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr return cmd.Run() } diff --git a/tool/kratos/template.go b/tool/kratos/template.go index 920d604cb..b7562c676 100644 --- a/tool/kratos/template.go +++ b/tool/kratos/template.go @@ -98,7 +98,7 @@ func main() { switch s { case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: ctx, cancel := context.WithTimeout(context.Background(), 35*time.Second) - if err := grpcSrv.Shutdown(ctx); err != nil + if err := grpcSrv.Shutdown(ctx); err != nil { log.Error("grpcSrv.Shutdown error(%v)", err) } // grpc if err := httpSrv.Shutdown(ctx); err != nil { @@ -302,6 +302,15 @@ func (s *Service) SayHello(ctx context.Context, req *pb.HelloReq) (reply *empty. return } +// SayHelloURL bm demo func. +func (s *Service) SayHelloURL(ctx context.Context, req *pb.HelloReq) (reply *pb.HelloResp, err error) { + reply = &pb.HelloResp{ + Content: "hello " + req.Name, + } + fmt.Printf("hello url %s", req.Name) + return +} + // Ping ping the resource. func (s *Service) Ping(ctx context.Context) (err error) { return s.dao.Ping(ctx) @@ -317,6 +326,7 @@ func (s *Service) Close() { import ( "net/http" + pb "{{.Name}}/api" "{{.Name}}/internal/model" "{{.Name}}/internal/service" @@ -343,6 +353,7 @@ func New(s *service.Service) (engine *bm.Engine) { } svc = s engine = bm.DefaultServer(hc.Server) + pb.RegisterDemoBMServer(engine, svc) initRouter(engine) if err := engine.Start(); err != nil { panic(err) @@ -377,21 +388,11 @@ func howToStart(c *bm.Context) { _tplAPIProto = `// 定义项目 API 的 proto 文件 可以同时描述 gRPC 和 HTTP API // protobuf 文件参考: // - https://developers.google.com/protocol-buffers/ -// - TODO:待补充文档URL -// protobuf 生成 HTTP 工具: -// - TODO:待补充文档URL -// gRPC Golang Model: -// - TODO:待补充文档URL -// gRPC Golang Warden Gen: -// - TODO:待补充文档URL -// gRPC http 调试工具(无需pb文件): -// - TODO:待补充文档URL -// grpc 命令行调试工具(无需pb文件): -// - TODO:待补充文档URL syntax = "proto3"; -import "github.com/gogo/protobuf/gogoproto/gogo.proto"; +import "gogoproto/gogo.proto"; import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; // package 命名使用 {appid}.{version} 的方式, version 形如 v1, v2 .. package demo.service.v1; @@ -399,21 +400,29 @@ package demo.service.v1; // NOTE: 最后请删除这些无用的注释 (゜-゜)つロ option go_package = "api"; -// do not generate getXXX() method option (gogoproto.goproto_getters_all) = false; service Demo { rpc SayHello (HelloReq) returns (.google.protobuf.Empty); + rpc SayHelloURL(HelloReq) returns (HelloResp) { + option (google.api.http) = { + get:"/{{.Name}}/say_hello" + }; + }; } message HelloReq { string name = 1 [(gogoproto.moretags)='form:"name" validate:"required"']; } + +message HelloResp { + string Content = 1 [(gogoproto.jsontag) = 'content']; +} ` _tplAPIGenerate = `package api // 生成 gRPC 代码 -//go:generate kratos tool kprotoc +//go:generate kratos tool protoc api.proto ` _tplModel = `package model diff --git a/tool/kratos/tool.go b/tool/kratos/tool.go index 6f84e25f9..34129a504 100644 --- a/tool/kratos/tool.go +++ b/tool/kratos/tool.go @@ -130,6 +130,7 @@ func runTool(name, dir, cmd string, args []string) (err error) { // Tool . type Tool struct { Name string `json:"name"` + Alias string `json:"alias"` BuildTime time.Time `json:"build_time"` Install string `json:"install"` Summary string `json:"summary"` @@ -151,6 +152,7 @@ func (t Tool) install() { fmt.Fprintf(os.Stderr, color.RedString("%s: 自动安装失败详情请查看文档:%s\n", t.Name, toolDoc)) return } + fmt.Println(t.Install) cmds := strings.Split(t.Install, " ") if len(cmds) > 0 { if err := runTool(t.Name, path.Dir(t.toolPath()), cmds[0], cmds[1:]); err == nil { @@ -172,7 +174,7 @@ func (t Tool) updated() bool { } func (t Tool) toolPath() string { - return filepath.Join(goPath(), "bin", t.Name) + return filepath.Join(gopath(), "bin", t.Alias) } func (t Tool) installed() bool { @@ -180,7 +182,7 @@ func (t Tool) installed() bool { return err == nil } -func goPath() (gp string) { +func gopath() (gp string) { gopaths := strings.Split(os.Getenv("GOPATH"), ":") if len(gopaths) == 1 { return gopaths[0] diff --git a/tool/kratos/tool_index.go b/tool/kratos/tool_index.go index b88e5f5b5..7581fc388 100644 --- a/tool/kratos/tool_index.go +++ b/tool/kratos/tool_index.go @@ -5,6 +5,7 @@ import "time" var toolIndexs = []*Tool{ &Tool{ Name: "kratos", + Alias: "kratos", BuildTime: time.Date(2019, 4, 2, 0, 0, 0, 0, time.Local), Install: "go get -u github.com/bilibili/kratos/tool/kratos", Summary: "Kratos工具集本体", @@ -12,11 +13,21 @@ var toolIndexs = []*Tool{ Author: "kratos", }, &Tool{ - Name: "kprotoc", - BuildTime: time.Date(2019, 4, 2, 0, 0, 0, 0, time.Local), - Install: "bash -c ${GOPATH}/src/github.com/bilibili/kratos/tool/kprotoc/install_kprotoc.sh", - Summary: "快速方便生成pb.go的protoc封装,不支持windows,Linux请先安装protoc工具", - Platform: []string{"darwin", "linux"}, - Author: "https://github.com/tomwei7", + Name: "protoc", + Alias: "kratos-protoc", + BuildTime: time.Date(2019, 5, 4, 0, 0, 0, 0, time.Local), + Install: "go get -u github.com/bilibili/kratos/tool/krotos-protoc", + Summary: "快速方便生成pb.go的protoc封装,windows、Linux请先安装protoc工具", + Platform: []string{"darwin", "linux", "windows"}, + Author: "kratos", + }, + &Tool{ + Name: "swagger", + Alias: "swagger", + BuildTime: time.Date(2019, 5, 5, 0, 0, 0, 0, time.Local), + Install: "go get -u github.com/go-swagger/go-swagger/cmd/swagger", + Summary: "swagger api文档", + Platform: []string{"darwin", "linux", "windows"}, + Author: "goswagger.io", }, } diff --git a/tool/protobuf/pkg/extensions/gogoproto/Makefile b/tool/protobuf/pkg/extensions/gogoproto/Makefile new file mode 100644 index 000000000..2d6cd0d07 --- /dev/null +++ b/tool/protobuf/pkg/extensions/gogoproto/Makefile @@ -0,0 +1,9 @@ +regenerate: + go install go-common/vendor/github.com/golang/protobuf/protoc-gen-go + protoc --go_out=paths=source_relative:. gogo.proto + +restore: + cp gogo.pb.golden gogo.pb.go + +preserve: + cp gogo.pb.go gogo.pb.golden diff --git a/tool/protobuf/pkg/extensions/gogoproto/gogo.pb.go b/tool/protobuf/pkg/extensions/gogoproto/gogo.pb.go new file mode 100644 index 000000000..935c23ca8 --- /dev/null +++ b/tool/protobuf/pkg/extensions/gogoproto/gogo.pb.go @@ -0,0 +1,818 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: gogo.proto + +package gogoproto // import "github.com/bilibili/kratos/tool/protobuf/pkg/extensions/gogoproto" + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +var E_GoprotoEnumPrefix = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.EnumOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 62001, + Name: "gogoproto.goproto_enum_prefix", + Tag: "varint,62001,opt,name=goproto_enum_prefix,json=goprotoEnumPrefix", + Filename: "gogo.proto", +} + +var E_GoprotoEnumStringer = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.EnumOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 62021, + Name: "gogoproto.goproto_enum_stringer", + Tag: "varint,62021,opt,name=goproto_enum_stringer,json=goprotoEnumStringer", + Filename: "gogo.proto", +} + +var E_EnumStringer = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.EnumOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 62022, + Name: "gogoproto.enum_stringer", + Tag: "varint,62022,opt,name=enum_stringer,json=enumStringer", + Filename: "gogo.proto", +} + +var E_EnumCustomname = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.EnumOptions)(nil), + ExtensionType: (*string)(nil), + Field: 62023, + Name: "gogoproto.enum_customname", + Tag: "bytes,62023,opt,name=enum_customname,json=enumCustomname", + Filename: "gogo.proto", +} + +var E_Enumdecl = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.EnumOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 62024, + Name: "gogoproto.enumdecl", + Tag: "varint,62024,opt,name=enumdecl", + Filename: "gogo.proto", +} + +var E_EnumvalueCustomname = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.EnumValueOptions)(nil), + ExtensionType: (*string)(nil), + Field: 66001, + Name: "gogoproto.enumvalue_customname", + Tag: "bytes,66001,opt,name=enumvalue_customname,json=enumvalueCustomname", + Filename: "gogo.proto", +} + +var E_GoprotoGettersAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63001, + Name: "gogoproto.goproto_getters_all", + Tag: "varint,63001,opt,name=goproto_getters_all,json=goprotoGettersAll", + Filename: "gogo.proto", +} + +var E_GoprotoEnumPrefixAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63002, + Name: "gogoproto.goproto_enum_prefix_all", + Tag: "varint,63002,opt,name=goproto_enum_prefix_all,json=goprotoEnumPrefixAll", + Filename: "gogo.proto", +} + +var E_GoprotoStringerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63003, + Name: "gogoproto.goproto_stringer_all", + Tag: "varint,63003,opt,name=goproto_stringer_all,json=goprotoStringerAll", + Filename: "gogo.proto", +} + +var E_VerboseEqualAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63004, + Name: "gogoproto.verbose_equal_all", + Tag: "varint,63004,opt,name=verbose_equal_all,json=verboseEqualAll", + Filename: "gogo.proto", +} + +var E_FaceAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63005, + Name: "gogoproto.face_all", + Tag: "varint,63005,opt,name=face_all,json=faceAll", + Filename: "gogo.proto", +} + +var E_GostringAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63006, + Name: "gogoproto.gostring_all", + Tag: "varint,63006,opt,name=gostring_all,json=gostringAll", + Filename: "gogo.proto", +} + +var E_PopulateAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63007, + Name: "gogoproto.populate_all", + Tag: "varint,63007,opt,name=populate_all,json=populateAll", + Filename: "gogo.proto", +} + +var E_StringerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63008, + Name: "gogoproto.stringer_all", + Tag: "varint,63008,opt,name=stringer_all,json=stringerAll", + Filename: "gogo.proto", +} + +var E_OnlyoneAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63009, + Name: "gogoproto.onlyone_all", + Tag: "varint,63009,opt,name=onlyone_all,json=onlyoneAll", + Filename: "gogo.proto", +} + +var E_EqualAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63013, + Name: "gogoproto.equal_all", + Tag: "varint,63013,opt,name=equal_all,json=equalAll", + Filename: "gogo.proto", +} + +var E_DescriptionAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63014, + Name: "gogoproto.description_all", + Tag: "varint,63014,opt,name=description_all,json=descriptionAll", + Filename: "gogo.proto", +} + +var E_TestgenAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63015, + Name: "gogoproto.testgen_all", + Tag: "varint,63015,opt,name=testgen_all,json=testgenAll", + Filename: "gogo.proto", +} + +var E_BenchgenAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63016, + Name: "gogoproto.benchgen_all", + Tag: "varint,63016,opt,name=benchgen_all,json=benchgenAll", + Filename: "gogo.proto", +} + +var E_MarshalerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63017, + Name: "gogoproto.marshaler_all", + Tag: "varint,63017,opt,name=marshaler_all,json=marshalerAll", + Filename: "gogo.proto", +} + +var E_UnmarshalerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63018, + Name: "gogoproto.unmarshaler_all", + Tag: "varint,63018,opt,name=unmarshaler_all,json=unmarshalerAll", + Filename: "gogo.proto", +} + +var E_StableMarshalerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63019, + Name: "gogoproto.stable_marshaler_all", + Tag: "varint,63019,opt,name=stable_marshaler_all,json=stableMarshalerAll", + Filename: "gogo.proto", +} + +var E_SizerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63020, + Name: "gogoproto.sizer_all", + Tag: "varint,63020,opt,name=sizer_all,json=sizerAll", + Filename: "gogo.proto", +} + +var E_GoprotoEnumStringerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63021, + Name: "gogoproto.goproto_enum_stringer_all", + Tag: "varint,63021,opt,name=goproto_enum_stringer_all,json=goprotoEnumStringerAll", + Filename: "gogo.proto", +} + +var E_EnumStringerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63022, + Name: "gogoproto.enum_stringer_all", + Tag: "varint,63022,opt,name=enum_stringer_all,json=enumStringerAll", + Filename: "gogo.proto", +} + +var E_UnsafeMarshalerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63023, + Name: "gogoproto.unsafe_marshaler_all", + Tag: "varint,63023,opt,name=unsafe_marshaler_all,json=unsafeMarshalerAll", + Filename: "gogo.proto", +} + +var E_UnsafeUnmarshalerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63024, + Name: "gogoproto.unsafe_unmarshaler_all", + Tag: "varint,63024,opt,name=unsafe_unmarshaler_all,json=unsafeUnmarshalerAll", + Filename: "gogo.proto", +} + +var E_GoprotoExtensionsMapAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63025, + Name: "gogoproto.goproto_extensions_map_all", + Tag: "varint,63025,opt,name=goproto_extensions_map_all,json=goprotoExtensionsMapAll", + Filename: "gogo.proto", +} + +var E_GoprotoUnrecognizedAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63026, + Name: "gogoproto.goproto_unrecognized_all", + Tag: "varint,63026,opt,name=goproto_unrecognized_all,json=goprotoUnrecognizedAll", + Filename: "gogo.proto", +} + +var E_GogoprotoImport = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63027, + Name: "gogoproto.gogoproto_import", + Tag: "varint,63027,opt,name=gogoproto_import,json=gogoprotoImport", + Filename: "gogo.proto", +} + +var E_ProtosizerAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63028, + Name: "gogoproto.protosizer_all", + Tag: "varint,63028,opt,name=protosizer_all,json=protosizerAll", + Filename: "gogo.proto", +} + +var E_CompareAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63029, + Name: "gogoproto.compare_all", + Tag: "varint,63029,opt,name=compare_all,json=compareAll", + Filename: "gogo.proto", +} + +var E_TypedeclAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63030, + Name: "gogoproto.typedecl_all", + Tag: "varint,63030,opt,name=typedecl_all,json=typedeclAll", + Filename: "gogo.proto", +} + +var E_EnumdeclAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63031, + Name: "gogoproto.enumdecl_all", + Tag: "varint,63031,opt,name=enumdecl_all,json=enumdeclAll", + Filename: "gogo.proto", +} + +var E_GoprotoRegistration = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63032, + Name: "gogoproto.goproto_registration", + Tag: "varint,63032,opt,name=goproto_registration,json=goprotoRegistration", + Filename: "gogo.proto", +} + +var E_MessagenameAll = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63033, + Name: "gogoproto.messagename_all", + Tag: "varint,63033,opt,name=messagename_all,json=messagenameAll", + Filename: "gogo.proto", +} + +var E_GoprotoGetters = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64001, + Name: "gogoproto.goproto_getters", + Tag: "varint,64001,opt,name=goproto_getters,json=goprotoGetters", + Filename: "gogo.proto", +} + +var E_GoprotoStringer = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64003, + Name: "gogoproto.goproto_stringer", + Tag: "varint,64003,opt,name=goproto_stringer,json=goprotoStringer", + Filename: "gogo.proto", +} + +var E_VerboseEqual = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64004, + Name: "gogoproto.verbose_equal", + Tag: "varint,64004,opt,name=verbose_equal,json=verboseEqual", + Filename: "gogo.proto", +} + +var E_Face = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64005, + Name: "gogoproto.face", + Tag: "varint,64005,opt,name=face", + Filename: "gogo.proto", +} + +var E_Gostring = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64006, + Name: "gogoproto.gostring", + Tag: "varint,64006,opt,name=gostring", + Filename: "gogo.proto", +} + +var E_Populate = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64007, + Name: "gogoproto.populate", + Tag: "varint,64007,opt,name=populate", + Filename: "gogo.proto", +} + +var E_Stringer = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 67008, + Name: "gogoproto.stringer", + Tag: "varint,67008,opt,name=stringer", + Filename: "gogo.proto", +} + +var E_Onlyone = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64009, + Name: "gogoproto.onlyone", + Tag: "varint,64009,opt,name=onlyone", + Filename: "gogo.proto", +} + +var E_Equal = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64013, + Name: "gogoproto.equal", + Tag: "varint,64013,opt,name=equal", + Filename: "gogo.proto", +} + +var E_Description = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64014, + Name: "gogoproto.description", + Tag: "varint,64014,opt,name=description", + Filename: "gogo.proto", +} + +var E_Testgen = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64015, + Name: "gogoproto.testgen", + Tag: "varint,64015,opt,name=testgen", + Filename: "gogo.proto", +} + +var E_Benchgen = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64016, + Name: "gogoproto.benchgen", + Tag: "varint,64016,opt,name=benchgen", + Filename: "gogo.proto", +} + +var E_Marshaler = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64017, + Name: "gogoproto.marshaler", + Tag: "varint,64017,opt,name=marshaler", + Filename: "gogo.proto", +} + +var E_Unmarshaler = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64018, + Name: "gogoproto.unmarshaler", + Tag: "varint,64018,opt,name=unmarshaler", + Filename: "gogo.proto", +} + +var E_StableMarshaler = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64019, + Name: "gogoproto.stable_marshaler", + Tag: "varint,64019,opt,name=stable_marshaler,json=stableMarshaler", + Filename: "gogo.proto", +} + +var E_Sizer = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64020, + Name: "gogoproto.sizer", + Tag: "varint,64020,opt,name=sizer", + Filename: "gogo.proto", +} + +var E_UnsafeMarshaler = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64023, + Name: "gogoproto.unsafe_marshaler", + Tag: "varint,64023,opt,name=unsafe_marshaler,json=unsafeMarshaler", + Filename: "gogo.proto", +} + +var E_UnsafeUnmarshaler = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64024, + Name: "gogoproto.unsafe_unmarshaler", + Tag: "varint,64024,opt,name=unsafe_unmarshaler,json=unsafeUnmarshaler", + Filename: "gogo.proto", +} + +var E_GoprotoExtensionsMap = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64025, + Name: "gogoproto.goproto_extensions_map", + Tag: "varint,64025,opt,name=goproto_extensions_map,json=goprotoExtensionsMap", + Filename: "gogo.proto", +} + +var E_GoprotoUnrecognized = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64026, + Name: "gogoproto.goproto_unrecognized", + Tag: "varint,64026,opt,name=goproto_unrecognized,json=goprotoUnrecognized", + Filename: "gogo.proto", +} + +var E_Protosizer = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64028, + Name: "gogoproto.protosizer", + Tag: "varint,64028,opt,name=protosizer", + Filename: "gogo.proto", +} + +var E_Compare = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64029, + Name: "gogoproto.compare", + Tag: "varint,64029,opt,name=compare", + Filename: "gogo.proto", +} + +var E_Typedecl = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64030, + Name: "gogoproto.typedecl", + Tag: "varint,64030,opt,name=typedecl", + Filename: "gogo.proto", +} + +var E_Messagename = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64033, + Name: "gogoproto.messagename", + Tag: "varint,64033,opt,name=messagename", + Filename: "gogo.proto", +} + +var E_Nullable = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 65001, + Name: "gogoproto.nullable", + Tag: "varint,65001,opt,name=nullable", + Filename: "gogo.proto", +} + +var E_Embed = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 65002, + Name: "gogoproto.embed", + Tag: "varint,65002,opt,name=embed", + Filename: "gogo.proto", +} + +var E_Customtype = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 65003, + Name: "gogoproto.customtype", + Tag: "bytes,65003,opt,name=customtype", + Filename: "gogo.proto", +} + +var E_Customname = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 65004, + Name: "gogoproto.customname", + Tag: "bytes,65004,opt,name=customname", + Filename: "gogo.proto", +} + +var E_Jsontag = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 65005, + Name: "gogoproto.jsontag", + Tag: "bytes,65005,opt,name=jsontag", + Filename: "gogo.proto", +} + +var E_Moretags = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 65006, + Name: "gogoproto.moretags", + Tag: "bytes,65006,opt,name=moretags", + Filename: "gogo.proto", +} + +var E_Casttype = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 65007, + Name: "gogoproto.casttype", + Tag: "bytes,65007,opt,name=casttype", + Filename: "gogo.proto", +} + +var E_Castkey = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 65008, + Name: "gogoproto.castkey", + Tag: "bytes,65008,opt,name=castkey", + Filename: "gogo.proto", +} + +var E_Castvalue = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 65009, + Name: "gogoproto.castvalue", + Tag: "bytes,65009,opt,name=castvalue", + Filename: "gogo.proto", +} + +var E_Stdtime = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 65010, + Name: "gogoproto.stdtime", + Tag: "varint,65010,opt,name=stdtime", + Filename: "gogo.proto", +} + +var E_Stdduration = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 65011, + Name: "gogoproto.stdduration", + Tag: "varint,65011,opt,name=stdduration", + Filename: "gogo.proto", +} + +func init() { + proto.RegisterExtension(E_GoprotoEnumPrefix) + proto.RegisterExtension(E_GoprotoEnumStringer) + proto.RegisterExtension(E_EnumStringer) + proto.RegisterExtension(E_EnumCustomname) + proto.RegisterExtension(E_Enumdecl) + proto.RegisterExtension(E_EnumvalueCustomname) + proto.RegisterExtension(E_GoprotoGettersAll) + proto.RegisterExtension(E_GoprotoEnumPrefixAll) + proto.RegisterExtension(E_GoprotoStringerAll) + proto.RegisterExtension(E_VerboseEqualAll) + proto.RegisterExtension(E_FaceAll) + proto.RegisterExtension(E_GostringAll) + proto.RegisterExtension(E_PopulateAll) + proto.RegisterExtension(E_StringerAll) + proto.RegisterExtension(E_OnlyoneAll) + proto.RegisterExtension(E_EqualAll) + proto.RegisterExtension(E_DescriptionAll) + proto.RegisterExtension(E_TestgenAll) + proto.RegisterExtension(E_BenchgenAll) + proto.RegisterExtension(E_MarshalerAll) + proto.RegisterExtension(E_UnmarshalerAll) + proto.RegisterExtension(E_StableMarshalerAll) + proto.RegisterExtension(E_SizerAll) + proto.RegisterExtension(E_GoprotoEnumStringerAll) + proto.RegisterExtension(E_EnumStringerAll) + proto.RegisterExtension(E_UnsafeMarshalerAll) + proto.RegisterExtension(E_UnsafeUnmarshalerAll) + proto.RegisterExtension(E_GoprotoExtensionsMapAll) + proto.RegisterExtension(E_GoprotoUnrecognizedAll) + proto.RegisterExtension(E_GogoprotoImport) + proto.RegisterExtension(E_ProtosizerAll) + proto.RegisterExtension(E_CompareAll) + proto.RegisterExtension(E_TypedeclAll) + proto.RegisterExtension(E_EnumdeclAll) + proto.RegisterExtension(E_GoprotoRegistration) + proto.RegisterExtension(E_MessagenameAll) + proto.RegisterExtension(E_GoprotoGetters) + proto.RegisterExtension(E_GoprotoStringer) + proto.RegisterExtension(E_VerboseEqual) + proto.RegisterExtension(E_Face) + proto.RegisterExtension(E_Gostring) + proto.RegisterExtension(E_Populate) + proto.RegisterExtension(E_Stringer) + proto.RegisterExtension(E_Onlyone) + proto.RegisterExtension(E_Equal) + proto.RegisterExtension(E_Description) + proto.RegisterExtension(E_Testgen) + proto.RegisterExtension(E_Benchgen) + proto.RegisterExtension(E_Marshaler) + proto.RegisterExtension(E_Unmarshaler) + proto.RegisterExtension(E_StableMarshaler) + proto.RegisterExtension(E_Sizer) + proto.RegisterExtension(E_UnsafeMarshaler) + proto.RegisterExtension(E_UnsafeUnmarshaler) + proto.RegisterExtension(E_GoprotoExtensionsMap) + proto.RegisterExtension(E_GoprotoUnrecognized) + proto.RegisterExtension(E_Protosizer) + proto.RegisterExtension(E_Compare) + proto.RegisterExtension(E_Typedecl) + proto.RegisterExtension(E_Messagename) + proto.RegisterExtension(E_Nullable) + proto.RegisterExtension(E_Embed) + proto.RegisterExtension(E_Customtype) + proto.RegisterExtension(E_Customname) + proto.RegisterExtension(E_Jsontag) + proto.RegisterExtension(E_Moretags) + proto.RegisterExtension(E_Casttype) + proto.RegisterExtension(E_Castkey) + proto.RegisterExtension(E_Castvalue) + proto.RegisterExtension(E_Stdtime) + proto.RegisterExtension(E_Stdduration) +} + +func init() { proto.RegisterFile("gogo.proto", fileDescriptor_gogo_e935c22a8aa82c87) } + +var fileDescriptor_gogo_e935c22a8aa82c87 = []byte{ + // 1260 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x98, 0xc9, 0x6f, 0x1c, 0x45, + 0x17, 0xc0, 0xf5, 0xe9, 0x4b, 0x14, 0xcf, 0xf3, 0x86, 0xc7, 0xc6, 0x84, 0x08, 0x44, 0xb8, 0x71, + 0xc1, 0x73, 0x40, 0x11, 0x4a, 0x59, 0x91, 0xe5, 0x58, 0x8e, 0x15, 0x84, 0xc1, 0x98, 0xd8, 0x6c, + 0x87, 0x51, 0xcf, 0x4c, 0xb9, 0x33, 0xa4, 0xbb, 0xab, 0xe9, 0xae, 0x8e, 0xe2, 0xdc, 0x50, 0x58, + 0x84, 0x10, 0x3b, 0x12, 0x24, 0x24, 0x81, 0x1c, 0xd8, 0xd7, 0xb0, 0x73, 0xe3, 0xc2, 0x72, 0xe5, + 0x7f, 0xe0, 0x02, 0x98, 0xdd, 0x37, 0x5f, 0xd0, 0xeb, 0x7e, 0xaf, 0xa7, 0x66, 0x3c, 0x52, 0xd5, + 0xdc, 0xda, 0x76, 0xfd, 0x7e, 0xae, 0x7e, 0xaf, 0xea, 0xbd, 0x37, 0x03, 0xe0, 0x2b, 0x5f, 0xcd, + 0xc4, 0x89, 0xd2, 0xaa, 0x5a, 0xc1, 0xe7, 0xfc, 0xf1, 0xc0, 0x41, 0x5f, 0x29, 0x3f, 0x90, 0xb5, + 0xfc, 0xa7, 0x46, 0xb6, 0x51, 0x6b, 0xc9, 0xb4, 0x99, 0xb4, 0x63, 0xad, 0x92, 0x62, 0xb1, 0xb8, + 0x0b, 0x26, 0x69, 0x71, 0x5d, 0x46, 0x59, 0x58, 0x8f, 0x13, 0xb9, 0xd1, 0x3e, 0x53, 0xbd, 0x61, + 0xa6, 0x20, 0x67, 0x98, 0x9c, 0x59, 0x8c, 0xb2, 0xf0, 0xee, 0x58, 0xb7, 0x55, 0x94, 0xee, 0xbf, + 0xfa, 0xf3, 0xff, 0x0f, 0xfe, 0xef, 0x96, 0xa1, 0xd5, 0x09, 0x42, 0xf1, 0x6f, 0x2b, 0x39, 0x28, + 0x56, 0xe1, 0xda, 0x2e, 0x5f, 0xaa, 0x93, 0x76, 0xe4, 0xcb, 0xc4, 0x62, 0xfc, 0x8e, 0x8c, 0x93, + 0x86, 0xf1, 0x5e, 0x42, 0xc5, 0x02, 0x8c, 0x0e, 0xe2, 0xfa, 0x9e, 0x5c, 0x23, 0xd2, 0x94, 0x2c, + 0xc1, 0x78, 0x2e, 0x69, 0x66, 0xa9, 0x56, 0x61, 0xe4, 0x85, 0xd2, 0xa2, 0xf9, 0x21, 0xd7, 0x54, + 0x56, 0xc7, 0x10, 0x5b, 0x28, 0x29, 0x21, 0x60, 0x08, 0x7f, 0xd3, 0x92, 0xcd, 0xc0, 0x62, 0xf8, + 0x91, 0x36, 0x52, 0xae, 0x17, 0xeb, 0x30, 0x85, 0xcf, 0xa7, 0xbd, 0x20, 0x93, 0xe6, 0x4e, 0x6e, + 0xee, 0xeb, 0x59, 0xc7, 0x65, 0x2c, 0xfb, 0xe9, 0xdc, 0x9e, 0x7c, 0x3b, 0x93, 0xa5, 0xc0, 0xd8, + 0x93, 0x91, 0x45, 0x5f, 0x6a, 0x2d, 0x93, 0xb4, 0xee, 0x05, 0xfd, 0xb6, 0x77, 0xac, 0x1d, 0x94, + 0xc6, 0xf3, 0x5b, 0xdd, 0x59, 0x5c, 0x2a, 0xc8, 0xf9, 0x20, 0x10, 0x6b, 0x70, 0x5d, 0x9f, 0x53, + 0xe1, 0xe0, 0xbc, 0x40, 0xce, 0xa9, 0x5d, 0x27, 0x03, 0xb5, 0x2b, 0xc0, 0xbf, 0x2f, 0x73, 0xe9, + 0xe0, 0x7c, 0x8d, 0x9c, 0x55, 0x62, 0x39, 0xa5, 0x68, 0xbc, 0x03, 0x26, 0x4e, 0xcb, 0xa4, 0xa1, + 0x52, 0x59, 0x97, 0x8f, 0x64, 0x5e, 0xe0, 0xa0, 0xbb, 0x48, 0xba, 0x71, 0x02, 0x17, 0x91, 0x43, + 0xd7, 0x61, 0x18, 0xda, 0xf0, 0x9a, 0xd2, 0x41, 0x71, 0x89, 0x14, 0xfb, 0x70, 0x3d, 0xa2, 0xf3, + 0x30, 0xe2, 0xab, 0xe2, 0x95, 0x1c, 0xf0, 0xcb, 0x84, 0x0f, 0x33, 0x43, 0x8a, 0x58, 0xc5, 0x59, + 0xe0, 0x69, 0x97, 0x1d, 0xbc, 0xce, 0x0a, 0x66, 0x48, 0x31, 0x40, 0x58, 0xdf, 0x60, 0x45, 0x6a, + 0xc4, 0x73, 0x0e, 0x86, 0x55, 0x14, 0x6c, 0xaa, 0xc8, 0x65, 0x13, 0x57, 0xc8, 0x00, 0x84, 0xa0, + 0x60, 0x16, 0x2a, 0xae, 0x89, 0x78, 0x73, 0x8b, 0xaf, 0x07, 0x67, 0x60, 0x09, 0xc6, 0xb9, 0x40, + 0xb5, 0x55, 0xe4, 0xa0, 0x78, 0x8b, 0x14, 0x63, 0x06, 0x46, 0xaf, 0xa1, 0x65, 0xaa, 0x7d, 0xe9, + 0x22, 0x79, 0x9b, 0x5f, 0x83, 0x10, 0x0a, 0x65, 0x43, 0x46, 0xcd, 0x93, 0x6e, 0x86, 0x77, 0x38, + 0x94, 0xcc, 0xa0, 0x62, 0x01, 0x46, 0x43, 0x2f, 0x49, 0x4f, 0x7a, 0x81, 0x53, 0x3a, 0xde, 0x25, + 0xc7, 0x48, 0x09, 0x51, 0x44, 0xb2, 0x68, 0x10, 0xcd, 0x7b, 0x1c, 0x11, 0x03, 0xa3, 0xab, 0x97, + 0x6a, 0xaf, 0x11, 0xc8, 0xfa, 0x20, 0xb6, 0xf7, 0xf9, 0xea, 0x15, 0xec, 0xb2, 0x69, 0x9c, 0x85, + 0x4a, 0xda, 0x3e, 0xeb, 0xa4, 0xf9, 0x80, 0x33, 0x9d, 0x03, 0x08, 0x3f, 0x00, 0xd7, 0xf7, 0x6d, + 0x13, 0x0e, 0xb2, 0x0f, 0x49, 0x36, 0xdd, 0xa7, 0x55, 0x50, 0x49, 0x18, 0x54, 0xf9, 0x11, 0x97, + 0x04, 0xd9, 0xe3, 0x5a, 0x81, 0xa9, 0x2c, 0x4a, 0xbd, 0x8d, 0xc1, 0xa2, 0xf6, 0x31, 0x47, 0xad, + 0x60, 0xbb, 0xa2, 0x76, 0x02, 0xa6, 0xc9, 0x38, 0x58, 0x5e, 0x3f, 0xe1, 0xc2, 0x5a, 0xd0, 0x6b, + 0xdd, 0xd9, 0x7d, 0x08, 0x0e, 0x94, 0xe1, 0x3c, 0xa3, 0x65, 0x94, 0x22, 0x53, 0x0f, 0xbd, 0xd8, + 0xc1, 0x7c, 0x95, 0xcc, 0x5c, 0xf1, 0x17, 0x4b, 0xc1, 0xb2, 0x17, 0xa3, 0xfc, 0x7e, 0xd8, 0xcf, + 0xf2, 0x2c, 0x4a, 0x64, 0x53, 0xf9, 0x51, 0xfb, 0xac, 0x6c, 0x39, 0xa8, 0x3f, 0xed, 0x49, 0xd5, + 0x9a, 0x81, 0xa3, 0xf9, 0x38, 0x5c, 0x53, 0xce, 0x2a, 0xf5, 0x76, 0x18, 0xab, 0x44, 0x5b, 0x8c, + 0x9f, 0x71, 0xa6, 0x4a, 0xee, 0x78, 0x8e, 0x89, 0x45, 0x18, 0xcb, 0x7f, 0x74, 0x3d, 0x92, 0x9f, + 0x93, 0x68, 0xb4, 0x43, 0x51, 0xe1, 0x68, 0xaa, 0x30, 0xf6, 0x12, 0x97, 0xfa, 0xf7, 0x05, 0x17, + 0x0e, 0x42, 0xa8, 0x70, 0xe8, 0xcd, 0x58, 0x62, 0xb7, 0x77, 0x30, 0x7c, 0xc9, 0x85, 0x83, 0x19, + 0x52, 0xf0, 0xc0, 0xe0, 0xa0, 0xf8, 0x8a, 0x15, 0xcc, 0xa0, 0xe2, 0x9e, 0x4e, 0xa3, 0x4d, 0xa4, + 0xdf, 0x4e, 0x75, 0xe2, 0xe1, 0x6a, 0x8b, 0xea, 0xeb, 0xad, 0xee, 0x21, 0x6c, 0xd5, 0x40, 0xb1, + 0x12, 0x85, 0x32, 0x4d, 0x3d, 0x5f, 0xe2, 0xc4, 0xe1, 0xb0, 0xb1, 0x6f, 0xb8, 0x12, 0x19, 0x58, + 0x71, 0x3f, 0xc7, 0x7b, 0x66, 0x95, 0xea, 0x4d, 0xbb, 0x44, 0xcb, 0x05, 0xc3, 0xae, 0x47, 0xb7, + 0xc9, 0xd5, 0x3d, 0xaa, 0x88, 0x3b, 0xf1, 0x00, 0x75, 0x0f, 0x14, 0x76, 0xd9, 0xb9, 0xed, 0xf2, + 0x0c, 0x75, 0xcd, 0x13, 0xe2, 0x18, 0x8c, 0x76, 0x0d, 0x13, 0x76, 0xd5, 0x63, 0xa4, 0x1a, 0x31, + 0x67, 0x09, 0x71, 0x08, 0xf6, 0xe0, 0x60, 0x60, 0xc7, 0x1f, 0x27, 0x3c, 0x5f, 0x2e, 0x8e, 0xc0, + 0x10, 0x0f, 0x04, 0x76, 0xf4, 0x09, 0x42, 0x4b, 0x04, 0x71, 0x1e, 0x06, 0xec, 0xf8, 0x93, 0x8c, + 0x33, 0x82, 0xb8, 0x7b, 0x08, 0xbf, 0x7d, 0x7a, 0x0f, 0x15, 0x74, 0x8e, 0xdd, 0x2c, 0xec, 0xa3, + 0x29, 0xc0, 0x4e, 0x3f, 0x45, 0xff, 0x9c, 0x09, 0x71, 0x3b, 0xec, 0x75, 0x0c, 0xf8, 0x33, 0x84, + 0x16, 0xeb, 0xc5, 0x02, 0x0c, 0x1b, 0x9d, 0xdf, 0x8e, 0x3f, 0x4b, 0xb8, 0x49, 0xe1, 0xd6, 0xa9, + 0xf3, 0xdb, 0x05, 0xcf, 0xf1, 0xd6, 0x89, 0xc0, 0xb0, 0x71, 0xd3, 0xb7, 0xd3, 0xcf, 0x73, 0xd4, + 0x19, 0x11, 0x73, 0x50, 0x29, 0x0b, 0xb9, 0x9d, 0x7f, 0x81, 0xf8, 0x0e, 0x83, 0x11, 0x30, 0x1a, + 0x89, 0x5d, 0xf1, 0x22, 0x47, 0xc0, 0xa0, 0xf0, 0x1a, 0xf5, 0x0e, 0x07, 0x76, 0xd3, 0x4b, 0x7c, + 0x8d, 0x7a, 0x66, 0x03, 0xcc, 0x66, 0x5e, 0x4f, 0xed, 0x8a, 0x97, 0x39, 0x9b, 0xf9, 0x7a, 0xdc, + 0x46, 0x6f, 0xb7, 0xb5, 0x3b, 0x5e, 0xe1, 0x6d, 0xf4, 0x34, 0x5b, 0xb1, 0x02, 0xd5, 0xdd, 0x9d, + 0xd6, 0xee, 0x7b, 0x95, 0x7c, 0x13, 0xbb, 0x1a, 0xad, 0xb8, 0x0f, 0xa6, 0xfb, 0x77, 0x59, 0xbb, + 0xf5, 0xfc, 0x76, 0xcf, 0xe7, 0x22, 0xb3, 0xc9, 0x8a, 0x13, 0x9d, 0x72, 0x6d, 0x76, 0x58, 0xbb, + 0xf6, 0xc2, 0x76, 0x77, 0xc5, 0x36, 0x1b, 0xac, 0x98, 0x07, 0xe8, 0x34, 0x37, 0xbb, 0xeb, 0x22, + 0xb9, 0x0c, 0x08, 0xaf, 0x06, 0xf5, 0x36, 0x3b, 0x7f, 0x89, 0xaf, 0x06, 0x11, 0x78, 0x35, 0xb8, + 0xad, 0xd9, 0xe9, 0xcb, 0x7c, 0x35, 0x18, 0xc1, 0x93, 0x6d, 0x74, 0x0e, 0xbb, 0xe1, 0x0a, 0x9f, + 0x6c, 0x83, 0x12, 0xb3, 0x30, 0x14, 0x65, 0x41, 0x80, 0x07, 0xb4, 0x7a, 0x63, 0x9f, 0x76, 0x25, + 0x83, 0x16, 0xf3, 0xbf, 0xec, 0xd0, 0x0e, 0x18, 0x10, 0x87, 0x60, 0xaf, 0x0c, 0x1b, 0xb2, 0x65, + 0x23, 0x7f, 0xdd, 0xe1, 0xa2, 0x84, 0xab, 0xc5, 0x1c, 0x40, 0xf1, 0xd1, 0x1e, 0x5f, 0xc5, 0xc6, + 0xfe, 0xb6, 0x53, 0x7c, 0xcb, 0x60, 0x20, 0x1d, 0x41, 0xfe, 0xe2, 0x16, 0xc1, 0x56, 0xb7, 0x20, + 0x7f, 0xeb, 0xc3, 0xb0, 0xef, 0xe1, 0x54, 0x45, 0xda, 0xf3, 0x6d, 0xf4, 0xef, 0x44, 0xf3, 0x7a, + 0x0c, 0x58, 0xa8, 0x12, 0xa9, 0x3d, 0x3f, 0xb5, 0xb1, 0x7f, 0x10, 0x5b, 0x02, 0x08, 0x37, 0xbd, + 0x54, 0xbb, 0xbc, 0xf7, 0x9f, 0x0c, 0x33, 0x80, 0x9b, 0xc6, 0xe7, 0x53, 0x72, 0xd3, 0xc6, 0xfe, + 0xc5, 0x9b, 0xa6, 0xf5, 0xe2, 0x08, 0x54, 0xf0, 0x31, 0xff, 0x56, 0xc4, 0x06, 0xff, 0x4d, 0x70, + 0x87, 0xc0, 0xff, 0x9c, 0xea, 0x96, 0x6e, 0xdb, 0x83, 0xfd, 0x0f, 0x65, 0x9a, 0xd7, 0x8b, 0x79, + 0x18, 0x4e, 0x75, 0xab, 0x95, 0xd1, 0x7c, 0x65, 0xc1, 0xff, 0xdd, 0x29, 0x3f, 0x72, 0x97, 0xcc, + 0xd1, 0x75, 0x98, 0x6c, 0xaa, 0xb0, 0x17, 0x3c, 0x0a, 0x4b, 0x6a, 0x49, 0xad, 0xe4, 0x57, 0xf1, + 0xc1, 0xdb, 0x7c, 0x75, 0x6b, 0x53, 0x85, 0xa1, 0x8a, 0x6a, 0x5e, 0x1c, 0xd7, 0xb4, 0x52, 0x41, + 0xad, 0x11, 0xe6, 0x4b, 0x6b, 0xf1, 0x29, 0xbf, 0xd6, 0xa9, 0x46, 0xb5, 0x72, 0x2e, 0xfe, 0x2f, + 0x00, 0x00, 0xff, 0xff, 0x97, 0xb1, 0x98, 0x88, 0x13, 0x14, 0x00, 0x00, +} diff --git a/tool/protobuf/pkg/extensions/gogoproto/gogo.pb.golden b/tool/protobuf/pkg/extensions/gogoproto/gogo.pb.golden new file mode 100644 index 000000000..f6502e4b9 --- /dev/null +++ b/tool/protobuf/pkg/extensions/gogoproto/gogo.pb.golden @@ -0,0 +1,45 @@ +// Code generated by protoc-gen-go. +// source: gogo.proto +// DO NOT EDIT! + +package gogoproto + +import proto "github.com/gogo/protobuf/proto" +import json "encoding/json" +import math "math" +import google_protobuf "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" + +// Reference proto, json, and math imports to suppress error if they are not otherwise used. +var _ = proto.Marshal +var _ = &json.SyntaxError{} +var _ = math.Inf + +var E_Nullable = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 51235, + Name: "gogoproto.nullable", + Tag: "varint,51235,opt,name=nullable", +} + +var E_Embed = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 51236, + Name: "gogoproto.embed", + Tag: "varint,51236,opt,name=embed", +} + +var E_Customtype = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 51237, + Name: "gogoproto.customtype", + Tag: "bytes,51237,opt,name=customtype", +} + +func init() { + proto.RegisterExtension(E_Nullable) + proto.RegisterExtension(E_Embed) + proto.RegisterExtension(E_Customtype) +} diff --git a/tool/protobuf/pkg/extensions/gogoproto/gogo.proto b/tool/protobuf/pkg/extensions/gogoproto/gogo.proto new file mode 100644 index 000000000..a9b8f96a3 --- /dev/null +++ b/tool/protobuf/pkg/extensions/gogoproto/gogo.proto @@ -0,0 +1,136 @@ +// Protocol Buffers for Go with Gadgets +// +// Copyright (c) 2013, The GoGo Authors. All rights reserved. +// http://github.com/gogo/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; +package gogoproto; + +import "google/protobuf/descriptor.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "GoGoProtos"; +option go_package = "github.com/bilibili/kratos/tool/protobuf/pkg/extensions/gogoproto"; + +extend google.protobuf.EnumOptions { + optional bool goproto_enum_prefix = 62001; + optional bool goproto_enum_stringer = 62021; + optional bool enum_stringer = 62022; + optional string enum_customname = 62023; + optional bool enumdecl = 62024; +} + +extend google.protobuf.EnumValueOptions { + optional string enumvalue_customname = 66001; +} + +extend google.protobuf.FileOptions { + optional bool goproto_getters_all = 63001; + optional bool goproto_enum_prefix_all = 63002; + optional bool goproto_stringer_all = 63003; + optional bool verbose_equal_all = 63004; + optional bool face_all = 63005; + optional bool gostring_all = 63006; + optional bool populate_all = 63007; + optional bool stringer_all = 63008; + optional bool onlyone_all = 63009; + + optional bool equal_all = 63013; + optional bool description_all = 63014; + optional bool testgen_all = 63015; + optional bool benchgen_all = 63016; + optional bool marshaler_all = 63017; + optional bool unmarshaler_all = 63018; + optional bool stable_marshaler_all = 63019; + + optional bool sizer_all = 63020; + + optional bool goproto_enum_stringer_all = 63021; + optional bool enum_stringer_all = 63022; + + optional bool unsafe_marshaler_all = 63023; + optional bool unsafe_unmarshaler_all = 63024; + + optional bool goproto_extensions_map_all = 63025; + optional bool goproto_unrecognized_all = 63026; + optional bool gogoproto_import = 63027; + optional bool protosizer_all = 63028; + optional bool compare_all = 63029; + optional bool typedecl_all = 63030; + optional bool enumdecl_all = 63031; + + optional bool goproto_registration = 63032; + optional bool messagename_all = 63033; +} + +extend google.protobuf.MessageOptions { + optional bool goproto_getters = 64001; + optional bool goproto_stringer = 64003; + optional bool verbose_equal = 64004; + optional bool face = 64005; + optional bool gostring = 64006; + optional bool populate = 64007; + optional bool stringer = 67008; + optional bool onlyone = 64009; + + optional bool equal = 64013; + optional bool description = 64014; + optional bool testgen = 64015; + optional bool benchgen = 64016; + optional bool marshaler = 64017; + optional bool unmarshaler = 64018; + optional bool stable_marshaler = 64019; + + optional bool sizer = 64020; + + optional bool unsafe_marshaler = 64023; + optional bool unsafe_unmarshaler = 64024; + + optional bool goproto_extensions_map = 64025; + optional bool goproto_unrecognized = 64026; + + optional bool protosizer = 64028; + optional bool compare = 64029; + + optional bool typedecl = 64030; + + optional bool messagename = 64033; +} + +extend google.protobuf.FieldOptions { + optional bool nullable = 65001; + optional bool embed = 65002; + optional string customtype = 65003; + optional string customname = 65004; + optional string jsontag = 65005; + optional string moretags = 65006; + optional string casttype = 65007; + optional string castkey = 65008; + optional string castvalue = 65009; + + optional bool stdtime = 65010; + optional bool stdduration = 65011; +} diff --git a/tool/protobuf/pkg/extensions/google/api/annotations.proto b/tool/protobuf/pkg/extensions/google/api/annotations.proto new file mode 100644 index 000000000..85c361b47 --- /dev/null +++ b/tool/protobuf/pkg/extensions/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/tool/protobuf/pkg/extensions/google/api/http.proto b/tool/protobuf/pkg/extensions/google/api/http.proto new file mode 100644 index 000000000..2bd3a19bf --- /dev/null +++ b/tool/protobuf/pkg/extensions/google/api/http.proto @@ -0,0 +1,318 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parmeters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// `HttpRule` defines the mapping of an RPC method to one or more HTTP +// REST API methods. The mapping specifies how different portions of the RPC +// request message are mapped to URL path, URL query parameters, and +// HTTP request body. The mapping is typically specified as an +// `google.api.http` annotation on the RPC method, +// see "google/api/annotations.proto" for details. +// +// The mapping consists of a field specifying the path template and +// method kind. The path template can refer to fields in the request +// message, as in the example below which describes a REST GET +// operation on a resource collection of messages: +// +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http).get = "/v1/messages/{message_id}/{sub.subfield}"; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // mapped to the URL +// SubMessage sub = 2; // `sub.subfield` is url-mapped +// } +// message Message { +// string text = 1; // content of the resource +// } +// +// The same http annotation can alternatively be expressed inside the +// `GRPC API Configuration` YAML file. +// +// http: +// rules: +// - selector: .Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// This definition enables an automatic, bidrectional mapping of HTTP +// JSON to RPC. Example: +// +// HTTP | RPC +// -----|----- +// `GET /v1/messages/123456/foo` | `GetMessage(message_id: "123456" sub: SubMessage(subfield: "foo"))` +// +// In general, not only fields but also field paths can be referenced +// from a path pattern. Fields mapped to the path pattern cannot be +// repeated and must have a primitive (non-message) type. +// +// Any fields in the request message which are not bound by the path +// pattern automatically become (optional) HTTP query +// parameters. Assume the following definition of the request message: +// +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http).get = "/v1/messages/{message_id}"; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // mapped to the URL +// int64 revision = 2; // becomes a parameter +// SubMessage sub = 3; // `sub.subfield` becomes a parameter +// } +// +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | RPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: "foo"))` +// +// Note that fields which are mapped to HTTP parameters must have a +// primitive type or a repeated primitive type. Message types are not +// allowed. In the case of a repeated type, the parameter can be +// repeated in the URL, as in `...?param=A¶m=B`. +// +// For HTTP method kinds which allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// put: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | RPC +// -----|----- +// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// put: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | RPC +// -----|----- +// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice of +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// +// This enables the following two alternative HTTP JSON to RPC +// mappings: +// +// HTTP | RPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: "123456")` +// +// # Rules for HTTP mapping +// +// The rules for mapping HTTP path, query parameters, and body fields +// to the request message are as follows: +// +// 1. The `body` field specifies either `*` or a field path, or is +// omitted. If omitted, it indicates there is no HTTP request body. +// 2. Leaf fields (recursive expansion of nested messages in the +// request) can be classified into three types: +// (a) Matched in the URL template. +// (b) Covered by body (if body is `*`, everything except (a) fields; +// else everything under the body field) +// (c) All other fields. +// 3. URL query parameters found in the HTTP request are mapped to (c) fields. +// 4. Any body sent with an HTTP request can contain only (b) fields. +// +// The syntax of the path template is as follows: +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single path segment. The syntax `**` matches zero +// or more path segments, which must be the last part of the path except the +// `Verb`. The syntax `LITERAL` matches literal text in the path. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path, all characters +// except `[-_.~0-9a-zA-Z]` are percent-encoded. Such variables show up in the +// Discovery Document as `{var}`. +// +// If a variable contains one or more path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path, all +// characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. Such variables +// show up in the Discovery Document as `{+var}`. +// +// NOTE: While the single segment variable matches the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 +// Simple String Expansion, the multi segment variable **does not** match +// RFC 6570 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. +// +// NOTE: the field paths in variables and in the `body` must not refer to +// repeated fields or map fields. +message HttpRule { + // Selects methods to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Used for listing and getting information about resources. + string get = 2; + + // Used for updating a resource. + string put = 3; + + // Used for creating a resource. + string post = 4; + + // Used for deleting a resource. + string delete = 5; + + // Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP body, or + // `*` for mapping all fields not captured by the path pattern to the HTTP + // body. NOTE: the referred field must not be a repeated field and must be + // present at the top-level of request message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // body of response. Other response fields are ignored. When + // not set, the response message will be used as HTTP body of response. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/tool/protobuf/pkg/gen/main.go b/tool/protobuf/pkg/gen/main.go new file mode 100644 index 000000000..e68f6a212 --- /dev/null +++ b/tool/protobuf/pkg/gen/main.go @@ -0,0 +1,94 @@ +package gen + +import ( + "io" + "io/ioutil" + "os" + "strings" + "log" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/descriptor" + plugin "github.com/golang/protobuf/protoc-gen-go/plugin" +) + +// Generator ... +type Generator interface { + Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse +} + +// Main ... +func Main(g Generator) { + req := readGenRequest() + resp := g.Generate(req) + writeResponse(os.Stdout, resp) +} + +// FilesToGenerate ... +func FilesToGenerate(req *plugin.CodeGeneratorRequest) []*descriptor.FileDescriptorProto { + genFiles := make([]*descriptor.FileDescriptorProto, 0) +Outer: + for _, name := range req.FileToGenerate { + for _, f := range req.ProtoFile { + if f.GetName() == name { + genFiles = append(genFiles, f) + continue Outer + } + } + Fail("could not find file named", name) + } + + return genFiles +} + +func readGenRequest() *plugin.CodeGeneratorRequest { + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + Error(err, "reading input") + } + + req := new(plugin.CodeGeneratorRequest) + if err = proto.Unmarshal(data, req); err != nil { + Error(err, "parsing input proto") + } + + if len(req.FileToGenerate) == 0 { + Fail("no files to generate") + } + + return req +} + +func writeResponse(w io.Writer, resp *plugin.CodeGeneratorResponse) { + data, err := proto.Marshal(resp) + if err != nil { + Error(err, "marshaling response") + } + _, err = w.Write(data) + if err != nil { + Error(err, "writing response") + } +} + + +// Fail log and exit +func Fail(msgs ...string) { + s := strings.Join(msgs, " ") + log.Print("error:", s) + os.Exit(1) +} + +// Fail log and exit +func Info(msgs ...string) { + s := strings.Join(msgs, " ") + log.Print("info:", s) + os.Exit(1) +} + + +// Error log and exit +func Error(err error, msgs ...string) { + s := strings.Join(msgs, " ") + ":" + err.Error() + log.Print("error:", s) + os.Exit(1) +} \ No newline at end of file diff --git a/tool/protobuf/pkg/generator/command_line.go b/tool/protobuf/pkg/generator/command_line.go new file mode 100644 index 000000000..551a558de --- /dev/null +++ b/tool/protobuf/pkg/generator/command_line.go @@ -0,0 +1,71 @@ +package generator + +import ( + "fmt" + "strings" +) + +type ParamsBase struct { + ImportPrefix string // String to prefix to imported package file names. + ImportMap map[string]string // Mapping from .proto file name to import path. + //Tpl bool // generate service implementation template + ExplicitHTTP bool // Only generate for method that add http option +} + +type GeneratorParamsInterface interface { + GetBase() *ParamsBase + SetParam(key string, value string) error +} + +type BasicParam struct{ ParamsBase } + +func (b *BasicParam) GetBase() *ParamsBase { + return &b.ParamsBase +} +func (b *BasicParam) SetParam(key string, value string) error { + return nil +} + +func ParseGeneratorParams(parameter string, result GeneratorParamsInterface) error { + ps := make(map[string]string) + for _, p := range strings.Split(parameter, ",") { + if p == "" { + continue + } + i := strings.Index(p, "=") + if i < 0 { + return fmt.Errorf("invalid parameter %q: expected format of parameter to be k=v", p) + } + k := p[0:i] + v := p[i+1:] + if v == "" { + return fmt.Errorf("invalid parameter %q: expected format of parameter to be k=v", k) + } + ps[k] = v + } + + if result.GetBase().ImportMap == nil { + result.GetBase().ImportMap = map[string]string{} + } + for k, v := range ps { + switch { + case k == "explicit_http": + if v == "true" || v == "1" { + result.GetBase().ExplicitHTTP = true + } + case k == "import_prefix": + result.GetBase().ImportPrefix = v + // Support import map 'M' prefix per https://github.com/golang/protobuf/blob/6fb5325/protoc-gen-go/generator/generator.go#L497. + case len(k) > 0 && k[0] == 'M': + result.GetBase().ImportMap[k[1:]] = v // 1 is the length of 'M'. + case len(k) > 0 && strings.HasPrefix(k, "go_import_mapping@"): + result.GetBase().ImportMap[k[18:]] = v // 18 is the length of 'go_import_mapping@'. + default: + e := result.SetParam(k, v) + if e != nil { + return e + } + } + } + return nil +} diff --git a/tool/protobuf/pkg/generator/generator.go b/tool/protobuf/pkg/generator/generator.go new file mode 100644 index 000000000..c1e2e4b7e --- /dev/null +++ b/tool/protobuf/pkg/generator/generator.go @@ -0,0 +1,310 @@ +package generator + +import ( + "bufio" + "bytes" + "fmt" + "go/parser" + "go/printer" + "go/token" + "path" + "strconv" + "strings" + + "github.com/bilibili/kratos/tool/protobuf/pkg/gen" + "github.com/bilibili/kratos/tool/protobuf/pkg/naming" + "github.com/bilibili/kratos/tool/protobuf/pkg/typemap" + "github.com/bilibili/kratos/tool/protobuf/pkg/utils" + "github.com/golang/protobuf/protoc-gen-go/descriptor" + plugin "github.com/golang/protobuf/protoc-gen-go/plugin" + "github.com/pkg/errors" +) + +const Version = "v0.1" + +var GoModuleImportPath = "github.com/bilibili/kratos" +var GoModuleDirName = "github.com/bilibili/kratos" + +type Base struct { + Reg *typemap.Registry + + // Map to record whether we've built each package + // pkgName => alias name + pkgs map[string]string + pkgNamesInUse map[string]bool + + ImportPrefix string // String to prefix to imported package file names. + importMap map[string]string // Mapping from .proto file name to import path. + + // Package naming: + GenPkgName string // Name of the package that we're generating + PackageName string // Name of the proto file package + fileToGoPackageName map[*descriptor.FileDescriptorProto]string + + // List of files that were inputs to the generator. We need to hold this in + // the struct so we can write a header for the file that lists its inputs. + GenFiles []*descriptor.FileDescriptorProto + + // Output buffer that holds the bytes we want to write out for a single file. + // Gets reset after working on a file. + Output *bytes.Buffer + + // key: pkgName + // value: importPath + Deps map[string]string + + Params *ParamsBase + + httpInfoCache map[string]*HTTPInfo +} + +// RegisterPackageName name is the go package name or proto pkg name +// return go pkg alias +func (t *Base) RegisterPackageName(name string) (alias string) { + alias = name + i := 1 + for t.pkgNamesInUse[alias] { + alias = name + strconv.Itoa(i) + i++ + } + t.pkgNamesInUse[alias] = true + t.pkgs[name] = alias + return alias +} + +func (t *Base) Setup(in *plugin.CodeGeneratorRequest, paramsOpt ...GeneratorParamsInterface) { + t.httpInfoCache = make(map[string]*HTTPInfo) + t.pkgs = make(map[string]string) + t.pkgNamesInUse = make(map[string]bool) + t.importMap = make(map[string]string) + t.Deps = make(map[string]string) + t.fileToGoPackageName = make(map[*descriptor.FileDescriptorProto]string) + t.Output = bytes.NewBuffer(nil) + + var params GeneratorParamsInterface + if len(paramsOpt) > 0 { + params = paramsOpt[0] + } else { + params = &BasicParam{} + } + err := ParseGeneratorParams(in.GetParameter(), params) + if err != nil { + gen.Fail("could not parse parameters", err.Error()) + } + t.Params = params.GetBase() + t.ImportPrefix = params.GetBase().ImportPrefix + t.importMap = params.GetBase().ImportMap + + t.GenFiles = gen.FilesToGenerate(in) + + // Collect information on types. + t.Reg = typemap.New(in.ProtoFile) + t.RegisterPackageName("context") + t.RegisterPackageName("ioutil") + t.RegisterPackageName("proto") + // Time to figure out package names of objects defined in protobuf. First, + // we'll figure out the name for the package we're generating. + genPkgName, err := DeduceGenPkgName(t.GenFiles) + if err != nil { + gen.Fail(err.Error()) + } + t.GenPkgName = genPkgName + // Next, we need to pick names for all the files that are dependencies. + if len(in.ProtoFile) > 0 { + t.PackageName = t.GenFiles[0].GetPackage() + } + + for _, f := range in.ProtoFile { + if fileDescSliceContains(t.GenFiles, f) { + // This is a file we are generating. It gets the shared package name. + t.fileToGoPackageName[f] = t.GenPkgName + } else { + // This is a dependency. Use its package name. + name := f.GetPackage() + if name == "" { + name = utils.BaseName(f.GetName()) + } + name = utils.CleanIdentifier(name) + alias := t.RegisterPackageName(name) + t.fileToGoPackageName[f] = alias + } + } + + for _, f := range t.GenFiles { + deps := t.DeduceDeps(f) + for k, v := range deps { + t.Deps[k] = v + } + } +} + +func (t *Base) DeduceDeps(file *descriptor.FileDescriptorProto) map[string]string { + deps := make(map[string]string) // Map of package name to quoted import path. + ourImportPath := path.Dir(naming.GoFileName(file, "")) + for _, s := range file.Service { + for _, m := range s.Method { + defs := []*typemap.MessageDefinition{ + t.Reg.MethodInputDefinition(m), + t.Reg.MethodOutputDefinition(m), + } + for _, def := range defs { + if def.File.GetPackage() == t.PackageName { + continue + } + // By default, import path is the dirname of the Go filename. + importPath := path.Dir(naming.GoFileName(def.File, "")) + if importPath == ourImportPath { + continue + } + importPath = t.SubstituteImportPath(importPath, def.File.GetName()) + importPath = t.ImportPrefix + importPath + pkg := t.GoPackageNameForProtoFile(def.File) + deps[pkg] = strconv.Quote(importPath) + } + } + } + return deps +} + +// DeduceGenPkgName figures out the go package name to use for generated code. +// Will try to use the explicit go_package setting in a file (if set, must be +// consistent in all files). If no files have go_package set, then use the +// protobuf package name (must be consistent in all files) +func DeduceGenPkgName(genFiles []*descriptor.FileDescriptorProto) (string, error) { + var genPkgName string + for _, f := range genFiles { + name, explicit := naming.GoPackageName(f) + if explicit { + name = utils.CleanIdentifier(name) + if genPkgName != "" && genPkgName != name { + // Make sure they're all set consistently. + return "", errors.Errorf("files have conflicting go_package settings, must be the same: %q and %q", genPkgName, name) + } + genPkgName = name + } + } + if genPkgName != "" { + return genPkgName, nil + } + + // If there is no explicit setting, then check the implicit package name + // (derived from the protobuf package name) of the files and make sure it's + // consistent. + for _, f := range genFiles { + name, _ := naming.GoPackageName(f) + name = utils.CleanIdentifier(name) + if genPkgName != "" && genPkgName != name { + return "", errors.Errorf("files have conflicting package names, must be the same or overridden with go_package: %q and %q", genPkgName, name) + } + genPkgName = name + } + + // All the files have the same name, so we're good. + return genPkgName, nil +} + +func (t *Base) GoPackageNameForProtoFile(file *descriptor.FileDescriptorProto) string { + return t.fileToGoPackageName[file] +} + +func fileDescSliceContains(slice []*descriptor.FileDescriptorProto, f *descriptor.FileDescriptorProto) bool { + for _, sf := range slice { + if f == sf { + return true + } + } + return false +} + +// P forwards to g.gen.P, which prints output. +func (t *Base) P(args ...string) { + for _, v := range args { + t.Output.WriteString(v) + } + t.Output.WriteByte('\n') +} + +func (t *Base) FormattedOutput() string { + // Reformat generated code. + fset := token.NewFileSet() + raw := t.Output.Bytes() + ast, err := parser.ParseFile(fset, "", raw, parser.ParseComments) + if err != nil { + // Print out the bad code with line numbers. + // This should never happen in practice, but it can while changing generated code, + // so consider this a debugging aid. + var src bytes.Buffer + s := bufio.NewScanner(bytes.NewReader(raw)) + for line := 1; s.Scan(); line++ { + fmt.Fprintf(&src, "%5d\t%s\n", line, s.Bytes()) + } + gen.Fail("bad Go source code was generated:", err.Error(), "\n"+src.String()) + } + + out := bytes.NewBuffer(nil) + err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(out, fset, ast) + if err != nil { + gen.Fail("generated Go source code could not be reformatted:", err.Error()) + } + + return out.String() +} + +func (t *Base) PrintComments(comments typemap.DefinitionComments) bool { + text := strings.TrimSuffix(comments.Leading, "\n") + if len(strings.TrimSpace(text)) == 0 { + return false + } + split := strings.Split(text, "\n") + for _, line := range split { + t.P("// ", strings.TrimPrefix(line, " ")) + } + return len(split) > 0 +} + +// IsOwnPackage ... +// protoName is fully qualified name of a type +func (t *Base) IsOwnPackage(protoName string) bool { + def := t.Reg.MessageDefinition(protoName) + if def == nil { + gen.Fail("could not find message for", protoName) + } + return def.File.GetPackage() == t.PackageName +} + +// Given a protobuf name for a Message, return the Go name we will use for that +// type, including its package prefix. +func (t *Base) GoTypeName(protoName string) string { + def := t.Reg.MessageDefinition(protoName) + if def == nil { + gen.Fail("could not find message for", protoName) + } + + var prefix string + if def.File.GetPackage() != t.PackageName { + prefix = t.GoPackageNameForProtoFile(def.File) + "." + } + + var name string + for _, parent := range def.Lineage() { + name += parent.Descriptor.GetName() + "_" + } + name += def.Descriptor.GetName() + return prefix + name +} + +func (t *Base) ShouldGenForMethod(file *descriptor.FileDescriptorProto, + service *descriptor.ServiceDescriptorProto, + method *descriptor.MethodDescriptorProto) bool { + if !t.Params.ExplicitHTTP { + return true + } + httpInfo := t.GetHttpInfoCached(file, service, method) + return httpInfo.HasExplicitHTTPPath +} +func (t *Base) SubstituteImportPath(importPath string, importFile string) string { + if substitution, ok := t.importMap[importFile]; ok { + importPath = substitution + } + return importPath +} diff --git a/tool/protobuf/pkg/generator/helper.go b/tool/protobuf/pkg/generator/helper.go new file mode 100644 index 000000000..6bdc5bdf1 --- /dev/null +++ b/tool/protobuf/pkg/generator/helper.go @@ -0,0 +1,136 @@ +package generator + +import ( + "reflect" + "strings" + + "github.com/bilibili/kratos/tool/protobuf/pkg/extensions/gogoproto" + "github.com/bilibili/kratos/tool/protobuf/pkg/tag" + "github.com/bilibili/kratos/tool/protobuf/pkg/typemap" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/descriptor" +) + +// GetJSONFieldName get name from gogoproto.jsontag +// else the original name +func GetJSONFieldName(field *descriptor.FieldDescriptorProto) string { + if field == nil { + return "" + } + if field.Options != nil { + v, err := proto.GetExtension(field.Options, gogoproto.E_Jsontag) + if err == nil && v.(*string) != nil { + ret := *(v.(*string)) + i := strings.Index(ret, ",") + if i != -1 { + ret = ret[:i] + } + return ret + } + } + return field.GetName() +} + +// GetFormOrJSONName get name from form tag, then json tag +// then original name +func GetFormOrJSONName(field *descriptor.FieldDescriptorProto) string { + if field == nil { + return "" + } + tags := tag.GetMoreTags(field) + if tags != nil { + tag := reflect.StructTag(*tags) + fName := tag.Get("form") + if fName != "" { + i := strings.Index(fName, ",") + if i != -1 { + fName = fName[:i] + } + return fName + } + } + return GetJSONFieldName(field) +} + +// IsScalar Is this field a scalar numeric type? +func IsScalar(field *descriptor.FieldDescriptorProto) bool { + if field.Type == nil { + return false + } + switch *field.Type { + case descriptor.FieldDescriptorProto_TYPE_DOUBLE, + descriptor.FieldDescriptorProto_TYPE_FLOAT, + descriptor.FieldDescriptorProto_TYPE_INT64, + descriptor.FieldDescriptorProto_TYPE_UINT64, + descriptor.FieldDescriptorProto_TYPE_INT32, + descriptor.FieldDescriptorProto_TYPE_FIXED64, + descriptor.FieldDescriptorProto_TYPE_FIXED32, + descriptor.FieldDescriptorProto_TYPE_BOOL, + descriptor.FieldDescriptorProto_TYPE_UINT32, + descriptor.FieldDescriptorProto_TYPE_ENUM, + descriptor.FieldDescriptorProto_TYPE_SFIXED32, + descriptor.FieldDescriptorProto_TYPE_SFIXED64, + descriptor.FieldDescriptorProto_TYPE_SINT32, + descriptor.FieldDescriptorProto_TYPE_SINT64, + descriptor.FieldDescriptorProto_TYPE_BYTES, + descriptor.FieldDescriptorProto_TYPE_STRING: + return true + default: + return false + } +} + +// IsMap is protocol buffer map +func IsMap(field *descriptor.FieldDescriptorProto, reg *typemap.Registry) bool { + if field.GetType() != descriptor.FieldDescriptorProto_TYPE_MESSAGE { + return false + } + md := reg.MessageDefinition(field.GetTypeName()) + if md == nil || !md.Descriptor.GetOptions().GetMapEntry() { + return false + } + return true +} + +// IsRepeated Is this field repeated? +func IsRepeated(field *descriptor.FieldDescriptorProto) bool { + return field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED + +} + +// GetFieldRequired is field required? +// eg. validate="required" +func GetFieldRequired( + f *descriptor.FieldDescriptorProto, + reg *typemap.Registry, + md *typemap.MessageDefinition, +) bool { + fComment, _ := reg.FieldComments(md, f) + var tags []reflect.StructTag + { + //get required info from gogoproto.moretags + moretags := tag.GetMoreTags(f) + if moretags != nil { + tags = []reflect.StructTag{reflect.StructTag(*moretags)} + } + } + if len(tags) == 0 { + tags = tag.GetTagsInComment(fComment.Leading) + } + validateTag := tag.GetTagValue("validate", tags) + var validateRules []string + if validateTag != "" { + validateRules = strings.Split(validateTag, ",") + } + required := false + for _, rule := range validateRules { + if rule == "required" { + required = true + } + } + return required +} + +func MakeIndentStr(i int) string { + return strings.Repeat(" ", i) +} diff --git a/tool/protobuf/pkg/generator/http.go b/tool/protobuf/pkg/generator/http.go new file mode 100644 index 000000000..d51357793 --- /dev/null +++ b/tool/protobuf/pkg/generator/http.go @@ -0,0 +1,145 @@ +package generator + +import ( + "fmt" + "net/http" + "strings" + + "github.com/bilibili/kratos/tool/protobuf/pkg/tag" + "github.com/bilibili/kratos/tool/protobuf/pkg/typemap" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/descriptor" + "google.golang.org/genproto/googleapis/api/annotations" +) + +// HTTPInfo http info for method +type HTTPInfo struct { + HttpMethod string + Path string + LegacyPath string + NewPath string + IsLegacyPath bool + Title string + Description string + // is http path added in the google.api.http option ? + HasExplicitHTTPPath bool +} + +type googleMethodOptionInfo struct { + Method string + PathPattern string + HTTPRule *annotations.HttpRule +} + +// GetHTTPInfo http info of method +func GetHTTPInfo( + file *descriptor.FileDescriptorProto, + service *descriptor.ServiceDescriptorProto, + method *descriptor.MethodDescriptorProto, + reg *typemap.Registry) *HTTPInfo { + var ( + title string + desc string + httpMethod string + newPath string + explicitHTTPPath bool + ) + comment, _ := reg.MethodComments(file, service, method) + tags := tag.GetTagsInComment(comment.Leading) + cleanComments := tag.GetCommentWithoutTag(comment.Leading) + if len(cleanComments) > 0 { + title = strings.Trim(cleanComments[0], "\n\r ") + if len(cleanComments) > 1 { + descLines := cleanComments[1:] + desc = strings.Trim(strings.Join(descLines, "\n"), "\r\n ") + } else { + desc = "" + } + } else { + title = "" + } + googleOptionInfo, err := ParseBMMethod(method) + if err == nil { + httpMethod = strings.ToUpper(googleOptionInfo.Method) + p := googleOptionInfo.PathPattern + if p != "" { + explicitHTTPPath = true + newPath = p + goto END + } + } + + if httpMethod == "" { + // resolve http method + httpMethod = tag.GetTagValue("method", tags) + if httpMethod == "" { + httpMethod = "GET" + } else { + httpMethod = strings.ToUpper(httpMethod) + } + } + + newPath = "/" + file.GetPackage() + "." + service.GetName() + "/" + method.GetName() +END: + var p = newPath + param := &HTTPInfo{HttpMethod: httpMethod, + Path: p, + NewPath: newPath, + IsLegacyPath: false, + Title: title, + Description: desc, + HasExplicitHTTPPath: explicitHTTPPath, + } + if title == "" { + param.Title = param.Path + } + return param +} + +func (t *Base) GetHttpInfoCached(file *descriptor.FileDescriptorProto, + service *descriptor.ServiceDescriptorProto, + method *descriptor.MethodDescriptorProto) *HTTPInfo { + key := file.GetPackage() + service.GetName() + method.GetName() + httpInfo, ok := t.httpInfoCache[key] + if !ok { + httpInfo = GetHTTPInfo(file, service, method, t.Reg) + t.httpInfoCache[key] = httpInfo + } + return httpInfo +} + +// ParseBMMethod parse BMMethodDescriptor form method descriptor proto +func ParseBMMethod(method *descriptor.MethodDescriptorProto) (*googleMethodOptionInfo, error) { + ext, err := proto.GetExtension(method.GetOptions(), annotations.E_Http) + if err != nil { + return nil, fmt.Errorf("get extension error: %s", err) + } + rule := ext.(*annotations.HttpRule) + var httpMethod string + var pathPattern string + switch pattern := rule.Pattern.(type) { + case *annotations.HttpRule_Get: + pathPattern = pattern.Get + httpMethod = http.MethodGet + case *annotations.HttpRule_Put: + pathPattern = pattern.Put + httpMethod = http.MethodPut + case *annotations.HttpRule_Post: + pathPattern = pattern.Post + httpMethod = http.MethodPost + case *annotations.HttpRule_Patch: + pathPattern = pattern.Patch + httpMethod = http.MethodPatch + case *annotations.HttpRule_Delete: + pathPattern = pattern.Delete + httpMethod = http.MethodDelete + default: + return nil, fmt.Errorf("unsupport http pattern %s", rule.Pattern) + } + bmMethod := &googleMethodOptionInfo{ + Method: httpMethod, + PathPattern: pathPattern, + HTTPRule: rule, + } + return bmMethod, nil +} diff --git a/tool/protobuf/pkg/naming/go_naming.go b/tool/protobuf/pkg/naming/go_naming.go new file mode 100644 index 000000000..3b428a6cf --- /dev/null +++ b/tool/protobuf/pkg/naming/go_naming.go @@ -0,0 +1,27 @@ +package naming + +import ( + "path" + + "github.com/golang/protobuf/protoc-gen-go/descriptor" +) + +// GoFileName returns the output name for the generated Go file. +func GoFileName(f *descriptor.FileDescriptorProto, suffix string) string { + name := *f.Name + if ext := path.Ext(name); ext == ".pb" || ext == ".proto" || ext == ".protodevel" { + name = name[:len(name)-len(ext)] + } + name += suffix + + // Does the file have a "go_package" option? If it does, it may override the + // filename. + if impPath, _, ok := goPackageOption(f); ok && impPath != "" { + // Replace the existing dirname with the declared import path. + _, name = path.Split(name) + name = path.Join(impPath, name) + return name + } + + return name +} diff --git a/tool/protobuf/pkg/naming/naming.go b/tool/protobuf/pkg/naming/naming.go new file mode 100644 index 000000000..7dc615c1c --- /dev/null +++ b/tool/protobuf/pkg/naming/naming.go @@ -0,0 +1,101 @@ +package naming + +import ( + "os" + "path/filepath" + "strings" + + "github.com/bilibili/kratos/tool/protobuf/pkg/utils" + "github.com/golang/protobuf/protoc-gen-go/descriptor" + "github.com/pkg/errors" + "github.com/siddontang/go/ioutil2" +) + +// GetVersionPrefix 根据go包名获取api版本前缀 +// @param pkg 从proto获取到的对应的go报名 +// @return 如果是v*开始的 返回v* +// 否则返回空 +func GetVersionPrefix(pkg string) string { + if pkg == "" { + return "" + } + if pkg[:1] == "v" { + return pkg + } + return "" +} + +func ServiceName(service *descriptor.ServiceDescriptorProto) string { + return utils.CamelCase(service.GetName()) +} + +// MethodName ... +func MethodName(method *descriptor.MethodDescriptorProto) string { + return utils.CamelCase(method.GetName()) +} + +// GetGoImportPathForPb 得到 proto 文件对应的 go import路径 +// protoFilename is the proto file name +// 可能根本无法得到proto文件的具体路径, 只能假设 proto 的filename 是相对当前目录的 +// 假设 protoAbsolutePath = wd/protoFilename +func GetGoImportPathForPb(protoFilename string, moduleImportPath string, moduleDirName string) (importPath string, err error) { + wd, err := os.Getwd() + if err != nil { + panic("cannot get working directory") + } + absPath := wd + "/" + protoFilename + if !ioutil2.FileExists(absPath) { + err = errors.New("Cannot find proto file path of " + protoFilename) + return "", err + } + index := strings.Index(absPath, moduleDirName) + if index == -1 { + return "", errors.Errorf("proto file %s is not inside project %s", protoFilename, moduleDirName) + } + relativePath := absPath[index:] + importPath = filepath.Dir(relativePath) + return importPath, nil +} + +// GoPackageNameForProtoFile returns the Go package name to use in the generated Go file. +// The result explicitly reports whether the name came from an option go_package +// statement. If explicit is false, the name was derived from the protocol +// buffer's package statement or the input file name. +func GoPackageName(f *descriptor.FileDescriptorProto) (name string, explicit bool) { + // Does the file have a "go_package" option? + if _, pkg, ok := goPackageOption(f); ok { + return pkg, true + } + + // Does the file have a package clause? + if pkg := f.GetPackage(); pkg != "" { + return pkg, false + } + // Use the file base name. + return utils.BaseName(f.GetName()), false +} + +// goPackageOption interprets the file's go_package option. +// If there is no go_package, it returns ("", "", false). +// If there's a simple name, it returns ("", pkg, true). +// If the option implies an import path, it returns (impPath, pkg, true). +func goPackageOption(f *descriptor.FileDescriptorProto) (impPath, pkg string, ok bool) { + pkg = f.GetOptions().GetGoPackage() + if pkg == "" { + return + } + ok = true + // The presence of a slash implies there's an import path. + slash := strings.LastIndex(pkg, "/") + if slash < 0 { + return + } + impPath, pkg = pkg, pkg[slash+1:] + // A semicolon-delimited suffix overrides the package name. + sc := strings.IndexByte(impPath, ';') + if sc < 0 { + return + } + impPath, pkg = impPath[:sc], impPath[sc+1:] + return +} diff --git a/tool/protobuf/pkg/project/project.go b/tool/protobuf/pkg/project/project.go new file mode 100644 index 000000000..69afdb87f --- /dev/null +++ b/tool/protobuf/pkg/project/project.go @@ -0,0 +1,119 @@ +package project + +import ( + "os" + "path/filepath" + "strings" + + "github.com/bilibili/kratos/tool/protobuf/pkg/utils" + "github.com/pkg/errors" + "github.com/siddontang/go/ioutil2" +) + +// if proto file is inside a project (that has a /api directory) +// this present a project info +// 必须假设proto文件的路径就是相对work-dir的路径,否则无法找到proto文件以及对应的project +type ProjectInfo struct { + // AbsolutePath of the project + AbsolutePath string + // ImportPath of project + ImportPath string + // dir name of project + Name string + // parent dir of project, maybe empty + Department string + // grandma dir of project , maybe empty + Typ string + HasInternalPkg bool + // 从工作目录(working directory)到project目录的相对路径 比如a/b .. ../a + // 作用是什么? + // 假设目录结构是 + // -project + // - api/api.proto + // - internal/service + // 我想在 internal/service下生成一个文件 service.go + // work-dir 为 project/api + // proto 生成命令为 protoc --xx_out=. api.proto + // 那么在 protoc plugin 中的文件输出路径就得是 ../internal/service/ => {pathRefToProj}internal/service + // + PathRefToProj string +} + +func NewProjInfo(file string, modDirName string, modImportPath string) (projInfo *ProjectInfo, err error) { + projInfo = &ProjectInfo{} + wd, err := os.Getwd() + if err != nil { + panic("cannot get working directory") + } + protoAbs := wd + "/" + file + protoAbs, _ = filepath.Abs(protoAbs) + + if !ioutil2.FileExists(protoAbs) { + return nil, errors.Errorf("Cannot find proto file in current dir %s, file: %s ", wd, file) + } + //appIndex := strings.Index(wd, modDirName) + //if appIndex == -1 { + // err = errors.New("not in " + modDirName) + // return nil, err + //} + + projPath := LookupProjPath(protoAbs) + + if projPath == "" { + err = errors.New("not in project") + return nil, err + } + rel, _ := filepath.Rel(wd, projPath) + projInfo.PathRefToProj = rel + projInfo.AbsolutePath = projPath + if ioutil2.FileExists(projPath + "/internal") { + projInfo.HasInternalPkg = true + } + + i := strings.Index(projInfo.AbsolutePath, modDirName) + if i == -1 { + err = errors.Errorf("project is not inside module, project=%s, module=%s", projPath, modDirName) + } + relativePath := projInfo.AbsolutePath[i+len(modDirName):] + projInfo.ImportPath = modImportPath + relativePath + projInfo.Name = filepath.Base(projPath) + if p := filepath.Dir(projPath); p != "/" { + projInfo.Department = filepath.Base(p) + if p = filepath.Dir(p); p != "/" { + projInfo.Typ = filepath.Base(p) + } + } + return projInfo, nil +} + +// LookupProjPath get project path by proto absolute path +// assume that proto is in the project's api directory +func LookupProjPath(protoAbs string) (result string) { + f := func(protoAbs string, dirs []string) string { + lastIndex := len(protoAbs) + curPath := protoAbs + + for lastIndex > 0 { + found := true + for _, d := range dirs { + if !utils.IsDir(curPath + "/" + d) { + found = false + break + } + } + if found { + return curPath + } + lastIndex = strings.LastIndex(curPath, string(os.PathSeparator)) + curPath = protoAbs[:lastIndex] + } + result = "" + return result + } + + firstStep := f(protoAbs, []string{"cmd", "api"}) + if firstStep != "" { + return firstStep + } + return f(protoAbs, []string{"api"}) +} diff --git a/tool/protobuf/pkg/tag/ext_tags.go b/tool/protobuf/pkg/tag/ext_tags.go new file mode 100644 index 000000000..b8b641d63 --- /dev/null +++ b/tool/protobuf/pkg/tag/ext_tags.go @@ -0,0 +1,20 @@ +package tag + +import ( + "github.com/bilibili/kratos/tool/protobuf/pkg/extensions/gogoproto" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/descriptor" +) + +func GetMoreTags(field *descriptor.FieldDescriptorProto) *string { + if field == nil { + return nil + } + if field.Options != nil { + v, err := proto.GetExtension(field.Options, gogoproto.E_Moretags) + if err == nil && v.(*string) != nil { + return v.(*string) + } + } + return nil +} diff --git a/tool/protobuf/pkg/tag/tags.go b/tool/protobuf/pkg/tag/tags.go new file mode 100644 index 000000000..e9f0e9c85 --- /dev/null +++ b/tool/protobuf/pkg/tag/tags.go @@ -0,0 +1,55 @@ +package tag + +import ( + "reflect" + "strings" +) + +// GetCommentWithoutTag strip tags in comment +func GetCommentWithoutTag(comment string) []string { + var lines []string + if comment == "" { + return lines + } + split := strings.Split(strings.TrimRight(comment, "\n\r"), "\n") + for _, line := range split { + tag, _, _ := GetLineTag(line) + if tag == "" { + lines = append(lines, line) + } + } + return lines +} + +func GetTagsInComment(comment string) []reflect.StructTag { + split := strings.Split(comment, "\n") + var tagsInComment []reflect.StructTag + for _, line := range split { + tag, _, _ := GetLineTag(line) + if tag != "" { + tagsInComment = append(tagsInComment, tag) + } + } + return tagsInComment +} + +func GetTagValue(key string, tags []reflect.StructTag) string { + for _, t := range tags { + val := t.Get(key) + if val != "" { + return val + } + } + return "" +} + +// find tag between backtick, start & end is the position of backtick +func GetLineTag(line string) (tag reflect.StructTag, start int, end int) { + start = strings.Index(line, "`") + end = strings.LastIndex(line, "`") + if end <= start { + return + } + tag = reflect.StructTag(line[start+1 : end]) + return +} diff --git a/tool/protobuf/pkg/typemap/typemap.go b/tool/protobuf/pkg/typemap/typemap.go new file mode 100644 index 000000000..27344750c --- /dev/null +++ b/tool/protobuf/pkg/typemap/typemap.go @@ -0,0 +1,277 @@ +package typemap + +// Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may not +// use this file except in compliance with the License. A copy of the License is +// located at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// or in the "license" file accompanying this file. This file is distributed on +// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +import ( + "strings" + + "github.com/golang/protobuf/protoc-gen-go/descriptor" + "github.com/pkg/errors" +) + +// Registry is the place of descriptors resolving +type Registry struct { + allFiles []*descriptor.FileDescriptorProto + filesByName map[string]*descriptor.FileDescriptorProto + + // Mapping of fully-qualified names to their definitions + messagesByProtoName map[string]*MessageDefinition +} + +// New Registry +func New(files []*descriptor.FileDescriptorProto) *Registry { + r := &Registry{ + allFiles: files, + filesByName: make(map[string]*descriptor.FileDescriptorProto), + messagesByProtoName: make(map[string]*MessageDefinition), + } + + // First, index the file descriptors by name. We need this so + // messageDefsForFile can correctly scan imports. + for _, f := range files { + r.filesByName[f.GetName()] = f + } + + // Next, index all the message definitions by their fully-qualified proto + // names. + for _, f := range files { + defs := messageDefsForFile(f, r.filesByName) + for name, def := range defs { + r.messagesByProtoName[name] = def + } + } + return r +} + +// FileComments comment of file +func (r *Registry) FileComments(file *descriptor.FileDescriptorProto) (DefinitionComments, error) { + return commentsAtPath([]int32{packagePath}, file), nil +} + +// ServiceComments comments of service +func (r *Registry) ServiceComments(file *descriptor.FileDescriptorProto, svc *descriptor.ServiceDescriptorProto) (DefinitionComments, error) { + for i, s := range file.Service { + if s == svc { + path := []int32{servicePath, int32(i)} + return commentsAtPath(path, file), nil + } + } + return DefinitionComments{}, errors.Errorf("service not found in file") +} + +// FieldComments ... +func (r *Registry) FieldComments(message *MessageDefinition, field *descriptor.FieldDescriptorProto) (DefinitionComments, error) { + file := message.File + mpath := message.path + for i, f := range message.Descriptor.Field { + if f == field { + path := append(mpath, messageFieldPath, int32(i)) + return commentsAtPath(path, file), nil + } + } + return DefinitionComments{}, errors.Errorf("field not found in msg") +} + +// MethodComments comment of method +func (r *Registry) MethodComments(file *descriptor.FileDescriptorProto, svc *descriptor.ServiceDescriptorProto, method *descriptor.MethodDescriptorProto) (DefinitionComments, error) { + for i, s := range file.Service { + if s == svc { + path := []int32{servicePath, int32(i)} + for j, m := range s.Method { + if m == method { + path = append(path, serviceMethodPath, int32(j)) + return commentsAtPath(path, file), nil + } + } + } + } + return DefinitionComments{}, errors.Errorf("service not found in file") +} + +// MethodInputDefinition returns MethodInputDefinition +func (r *Registry) MethodInputDefinition(method *descriptor.MethodDescriptorProto) *MessageDefinition { + return r.messagesByProtoName[method.GetInputType()] +} + +// MethodOutputDefinition returns MethodOutputDefinition +func (r *Registry) MethodOutputDefinition(method *descriptor.MethodDescriptorProto) *MessageDefinition { + return r.messagesByProtoName[method.GetOutputType()] +} + +// MessageDefinition by name +func (r *Registry) MessageDefinition(name string) *MessageDefinition { + return r.messagesByProtoName[name] +} + +// MessageDefinition msg info +type MessageDefinition struct { + // Descriptor is is the DescriptorProto defining the message. + Descriptor *descriptor.DescriptorProto + // File is the File that the message was defined in. Or, if it has been + // publicly imported, what File was that import performed in? + File *descriptor.FileDescriptorProto + // Parent is the parent message, if this was defined as a nested message. If + // this was defiend at the top level, parent is nil. + Parent *MessageDefinition + // Comments describes the comments surrounding a message's definition. If it + // was publicly imported, then these comments are from the actual source file, + // not the file that the import was performed in. + Comments DefinitionComments + + // path is the 'SourceCodeInfo' path. See the documentation for + // github.com/golang/protobuf/protoc-gen-go/descriptor.SourceCodeInfo for an + // explanation of its format. + path []int32 +} + +// ProtoName returns the dot-delimited, fully-qualified protobuf name of the +// message. +func (m *MessageDefinition) ProtoName() string { + prefix := "." + if pkg := m.File.GetPackage(); pkg != "" { + prefix += pkg + "." + } + + if lineage := m.Lineage(); len(lineage) > 0 { + for _, parent := range lineage { + prefix += parent.Descriptor.GetName() + "." + } + } + + return prefix + m.Descriptor.GetName() +} + +// Lineage returns m's parental chain all the way back up to a top-level message +// definition. The first element of the returned slice is the highest-level +// parent. +func (m *MessageDefinition) Lineage() []*MessageDefinition { + var parents []*MessageDefinition + for p := m.Parent; p != nil; p = p.Parent { + parents = append([]*MessageDefinition{p}, parents...) + } + return parents +} + +// descendants returns all the submessages defined within m, and all the +// descendants of those, recursively. +func (m *MessageDefinition) descendants() []*MessageDefinition { + descendants := make([]*MessageDefinition, 0) + for i, child := range m.Descriptor.NestedType { + path := append(m.path, []int32{messageMessagePath, int32(i)}...) + childDef := &MessageDefinition{ + Descriptor: child, + File: m.File, + Parent: m, + Comments: commentsAtPath(path, m.File), + path: path, + } + descendants = append(descendants, childDef) + descendants = append(descendants, childDef.descendants()...) + } + return descendants +} + +// messageDefsForFile gathers a mapping of fully-qualified protobuf names to +// their definitions. It scans a singles file at a time. It requires a mapping +// of .proto file names to their definitions in order to correctly handle +// 'import public' declarations; this mapping should include all files +// transitively imported by f. +func messageDefsForFile(f *descriptor.FileDescriptorProto, filesByName map[string]*descriptor.FileDescriptorProto) map[string]*MessageDefinition { + byProtoName := make(map[string]*MessageDefinition) + // First, gather all the messages defined at the top level. + for i, d := range f.MessageType { + path := []int32{messagePath, int32(i)} + def := &MessageDefinition{ + Descriptor: d, + File: f, + Parent: nil, + Comments: commentsAtPath(path, f), + path: path, + } + + byProtoName[def.ProtoName()] = def + // Next, all nested message definitions. + for _, child := range def.descendants() { + byProtoName[child.ProtoName()] = child + } + } + + // Finally, all messages imported publicly. + for _, depIdx := range f.PublicDependency { + depFileName := f.Dependency[depIdx] + depFile := filesByName[depFileName] + depDefs := messageDefsForFile(depFile, filesByName) + for _, def := range depDefs { + imported := &MessageDefinition{ + Descriptor: def.Descriptor, + File: f, + Parent: def.Parent, + Comments: commentsAtPath(def.path, depFile), + path: def.path, + } + byProtoName[imported.ProtoName()] = imported + } + } + + return byProtoName +} + +// // ignored detached comments. +type DefinitionComments struct { + Leading string + Trailing string + LeadingDetached []string +} + +func commentsAtPath(path []int32, sourceFile *descriptor.FileDescriptorProto) DefinitionComments { + if sourceFile.SourceCodeInfo == nil { + // The compiler didn't provide us with comments. + return DefinitionComments{} + } + + for _, loc := range sourceFile.SourceCodeInfo.Location { + if pathEqual(path, loc.Path) { + return DefinitionComments{ + Leading: strings.TrimSuffix(loc.GetLeadingComments(), "\n"), + LeadingDetached: loc.GetLeadingDetachedComments(), + Trailing: loc.GetTrailingComments(), + } + } + } + return DefinitionComments{} +} + +func pathEqual(path1, path2 []int32) bool { + if len(path1) != len(path2) { + return false + } + for i, v := range path1 { + if path2[i] != v { + return false + } + } + return true +} + +const ( + // tag numbers in FileDescriptorProto + packagePath = 2 // package + messagePath = 4 // message_type + servicePath = 6 // service + // tag numbers in DescriptorProto + messageFieldPath = 2 // field + messageMessagePath = 3 // nested_type + // tag numbers in ServiceDescriptorProto + serviceMethodPath = 2 // method +) diff --git a/tool/protobuf/pkg/utils/stringutils.go b/tool/protobuf/pkg/utils/stringutils.go new file mode 100644 index 000000000..d36704c6b --- /dev/null +++ b/tool/protobuf/pkg/utils/stringutils.go @@ -0,0 +1,98 @@ + +package utils + +import ( + "strings" + "unicode" +) + +// Is c an ASCII lower-case letter? +func isASCIILower(c byte) bool { + return 'a' <= c && c <= 'z' +} + +// Is c an ASCII digit? +func isASCIIDigit(c byte) bool { + return '0' <= c && c <= '9' +} + +// CamelCase converts a string from snake_case to CamelCased. +// +// If there is an interior underscore followed by a lower case letter, drop the +// underscore and convert the letter to upper case. There is a remote +// possibility of this rewrite causing a name collision, but it's so remote +// we're prepared to pretend it's nonexistent - since the C++ generator +// lowercases names, it's extremely unlikely to have two fields with different +// capitalizations. In short, _my_field_name_2 becomes XMyFieldName_2. +func CamelCase(s string) string { + if s == "" { + return "" + } + t := make([]byte, 0, 32) + i := 0 + if s[0] == '_' { + // Need a capital letter; drop the '_'. + t = append(t, 'X') + i++ + } + // Invariant: if the next letter is lower case, it must be converted + // to upper case. + // + // That is, we process a word at a time, where words are marked by _ or upper + // case letter. Digits are treated as words. + for ; i < len(s); i++ { + c := s[i] + if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { + continue // Skip the underscore in s. + } + if isASCIIDigit(c) { + t = append(t, c) + continue + } + // Assume we have a letter now - if not, it's a bogus identifier. The next + // word is a sequence of characters that must start upper case. + if isASCIILower(c) { + c ^= ' ' // Make it a capital letter. + } + t = append(t, c) // Guaranteed not lower case. + // Accept lower case sequence that follows. + for i+1 < len(s) && isASCIILower(s[i+1]) { + i++ + t = append(t, s[i]) + } + } + return string(t) +} + +// CamelCaseSlice is like CamelCase, but the argument is a slice of strings to +// be joined with "_" and then camelcased. +func CamelCaseSlice(elem []string) string { return CamelCase(strings.Join(elem, "_")) } + +// BaseName the last path element of a slash-delimited name, with the last +// dotted suffix removed. +func BaseName(name string) string { + // First, find the last element + if i := strings.LastIndex(name, "/"); i >= 0 { + name = name[i+1:] + } + // Now drop the suffix + if i := strings.LastIndex(name, "."); i >= 0 { + name = name[0:i] + } + return name +} + +// AlphaDigitize replaces non-letter, non-digit, non-underscore characters with +// underscore. +func AlphaDigitize(r rune) rune { + if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' { + return r + } + return '_' +} + +// CleanIdentifier makes sure s is a valid 'identifier' string: it contains only +// letters, numbers, and underscore. +func CleanIdentifier(s string) string { + return strings.Map(AlphaDigitize, s) +} \ No newline at end of file diff --git a/tool/protobuf/pkg/utils/utils.go b/tool/protobuf/pkg/utils/utils.go new file mode 100644 index 000000000..dcf0c3f14 --- /dev/null +++ b/tool/protobuf/pkg/utils/utils.go @@ -0,0 +1,29 @@ +package utils + +import ( + "os" + "unicode" +) + +// LcFirst lower the first letter +func LcFirst(str string) string { + for i, v := range str { + return string(unicode.ToLower(v)) + str[i+1:] + } + return "" +} + +func IsDir(name string) bool { + file, err := os.Open(name) + + if err != nil { + return false + } + defer file.Close() + + fi, err := file.Stat() + if err != nil { + return false + } + return fi.IsDir() +} diff --git a/tool/protobuf/protoc-gen-bm/generator/generator.go b/tool/protobuf/protoc-gen-bm/generator/generator.go new file mode 100644 index 000000000..6e7f5b744 --- /dev/null +++ b/tool/protobuf/protoc-gen-bm/generator/generator.go @@ -0,0 +1,355 @@ +package generator + +import ( + "fmt" + "reflect" + "sort" + "strings" + + "github.com/bilibili/kratos/tool/protobuf/pkg/generator" + "github.com/bilibili/kratos/tool/protobuf/pkg/naming" + "github.com/bilibili/kratos/tool/protobuf/pkg/tag" + "github.com/bilibili/kratos/tool/protobuf/pkg/typemap" + "github.com/bilibili/kratos/tool/protobuf/pkg/utils" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/descriptor" + plugin "github.com/golang/protobuf/protoc-gen-go/plugin" +) + +type bm struct { + generator.Base + filesHandled int +} + +// BmGenerator BM generator. +func BmGenerator() *bm { + t := &bm{} + return t +} + +// Generate ... +func (t *bm) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse { + t.Setup(in) + + // Showtime! Generate the response. + resp := new(plugin.CodeGeneratorResponse) + for _, f := range t.GenFiles { + respFile := t.generateForFile(f) + if respFile != nil { + resp.File = append(resp.File, respFile) + } + } + return resp +} + +func (t *bm) generateForFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File { + resp := new(plugin.CodeGeneratorResponse_File) + //if len(file.Service) == 0 { + // return nil + //} + + t.generateFileHeader(file, t.GenPkgName) + t.generateImports(file) + t.generatePathConstants(file) + count := 0 + for i, service := range file.Service { + count += t.generateBMInterface(file, service) + t.generateBMRoute(file, service, i) + } + //if count == 0 { + // return nil + //} + + resp.Name = proto.String(naming.GoFileName(file, ".bm.go")) + resp.Content = proto.String(t.FormattedOutput()) + t.Output.Reset() + + t.filesHandled++ + return resp +} + +func (t *bm) generatePathConstants(file *descriptor.FileDescriptorProto) { + t.P() + for _, service := range file.Service { + name := naming.ServiceName(service) + for _, method := range service.Method { + if !t.ShouldGenForMethod(file, service, method) { + continue + } + apiInfo := t.GetHttpInfoCached(file, service, method) + t.P(`var Path`, name, naming.MethodName(method), ` = "`, apiInfo.Path, `"`) + } + t.P() + } +} + +func (t *bm) generateFileHeader(file *descriptor.FileDescriptorProto, pkgName string) { + t.P("// Code generated by protoc-gen-bm ", generator.Version, ", DO NOT EDIT.") + t.P("// source: ", file.GetName()) + t.P() + if t.filesHandled == 0 { + // doc for the first file + t.P("/*") + t.P("Package ", t.GenPkgName, " is a generated blademaster stub package.") + t.P("This code was generated with kratos/tool/bmgen/protoc-gen-bm ", generator.Version, ".") + t.P() + comment, err := t.Reg.FileComments(file) + if err == nil && comment.Leading != "" { + for _, line := range strings.Split(comment.Leading, "\n") { + line = strings.TrimPrefix(line, " ") + // ensure we don't escape from the block comment + line = strings.Replace(line, "*/", "* /", -1) + t.P(line) + } + t.P() + } + t.P("It is generated from these files:") + for _, f := range t.GenFiles { + t.P("\t", f.GetName()) + } + t.P("*/") + } + t.P(`package `, pkgName) + t.P() +} + +func (t *bm) generateImports(file *descriptor.FileDescriptorProto) { + //if len(file.Service) == 0 { + // return + //} + t.P(`import (`) + //t.P(` `,t.pkgs["context"], ` "context"`) + t.P(` "context"`) + t.P() + t.P(` bm "github.com/bilibili/kratos/pkg/net/http/blademaster"`) + t.P(` "github.com/bilibili/kratos/pkg/net/http/blademaster/binding"`) + + t.P(`)`) + // It's legal to import a message and use it as an input or output for a + // method. Make sure to import the package of any such message. First, dedupe + // them. + deps := make(map[string]string) // Map of package name to quoted import path. + deps = t.DeduceDeps(file) + for pkg, importPath := range deps { + for _, service := range file.Service { + for _, method := range service.Method { + inputType := t.GoTypeName(method.GetInputType()) + outputType := t.GoTypeName(method.GetOutputType()) + if strings.HasPrefix(pkg, outputType) || strings.HasPrefix(pkg, inputType) { + t.P(`import `, pkg, ` `, importPath) + } + } + } + } + if len(deps) > 0 { + t.P() + } + t.P() + t.P(`// to suppressed 'imported but not used warning'`) + t.P(`var _ *bm.Context`) + t.P(`var _ context.Context`) + t.P(`var _ binding.StructValidator`) + +} + +// Big header comments to makes it easier to visually parse a generated file. +func (t *bm) sectionComment(sectionTitle string) { + t.P() + t.P(`// `, strings.Repeat("=", len(sectionTitle))) + t.P(`// `, sectionTitle) + t.P(`// `, strings.Repeat("=", len(sectionTitle))) + t.P() +} + +func (t *bm) generateBMRoute( + file *descriptor.FileDescriptorProto, + service *descriptor.ServiceDescriptorProto, + index int) { + // old mode is generate xx.route.go in the http pkg + // new mode is generate route code in the same .bm.go + // route rule /x{department}/{project-name}/{path_prefix}/method_name + // generate each route method + servName := naming.ServiceName(service) + versionPrefix := naming.GetVersionPrefix(t.GenPkgName) + svcName := utils.LcFirst(utils.CamelCase(versionPrefix)) + servName + "Svc" + t.P(`var `, svcName, ` `, servName, `BMServer`) + + type methodInfo struct { + midwares []string + routeFuncName string + apiInfo *generator.HTTPInfo + methodName string + } + var methList []methodInfo + var allMidwareMap = make(map[string]bool) + var isLegacyPkg = false + for _, method := range service.Method { + if !t.ShouldGenForMethod(file, service, method) { + continue + } + var midwares []string + comments, _ := t.Reg.MethodComments(file, service, method) + tags := tag.GetTagsInComment(comments.Leading) + if tag.GetTagValue("dynamic", tags) == "true" { + continue + } + apiInfo := t.GetHttpInfoCached(file, service, method) + isLegacyPkg = apiInfo.IsLegacyPath + //httpMethod, legacyPath, path := getHttpInfo(file, service, method, t.reg) + //if legacyPath != "" { + // isLegacyPkg = true + //} + + midStr := tag.GetTagValue("midware", tags) + if midStr != "" { + midwares = strings.Split(midStr, ",") + for _, m := range midwares { + allMidwareMap[m] = true + } + } + + methName := naming.MethodName(method) + inputType := t.GoTypeName(method.GetInputType()) + + routeName := utils.LcFirst(utils.CamelCase(servName) + + utils.CamelCase(methName)) + + methList = append(methList, methodInfo{ + apiInfo: apiInfo, + midwares: midwares, + routeFuncName: routeName, + methodName: method.GetName(), + }) + + t.P(fmt.Sprintf("func %s (c *bm.Context) {", routeName)) + t.P(` p := new(`, inputType, `)`) + requestBinding := "" + if t.hasHeaderTag(t.Reg.MessageDefinition(method.GetInputType())) { + requestBinding = ", binding.Request" + } + t.P(` if err := c.BindWith(p, binding.Default(c.Request.Method, c.Request.Header.Get("Content-Type"))` + + requestBinding + `); err != nil {`) + t.P(` return`) + t.P(` }`) + t.P(` resp, err := `, svcName, `.`, methName, `(c, p)`) + t.P(` c.JSON(resp, err)`) + t.P(`}`) + t.P(``) + } + + // generate route group + var midList []string + for m := range allMidwareMap { + midList = append(midList, m+" bm.HandlerFunc") + } + + sort.Strings(midList) + + // 注册老的路由的方法 + if isLegacyPkg { + funcName := `Register` + utils.CamelCase(versionPrefix) + servName + `Service` + t.P(`// `, funcName, ` Register the blademaster route with middleware map`) + t.P(`// midMap is the middleware map, the key is defined in proto`) + t.P(`func `, funcName, `(e *bm.Engine, svc `, servName, "BMServer, midMap map[string]bm.HandlerFunc)", ` {`) + var keys []string + for m := range allMidwareMap { + keys = append(keys, m) + } + // to keep generated code consistent + sort.Strings(keys) + for _, m := range keys { + t.P(m, ` := midMap["`, m, `"]`) + } + + t.P(svcName, ` = svc`) + for _, methInfo := range methList { + var midArgStr string + if len(methInfo.midwares) == 0 { + midArgStr = "" + } else { + midArgStr = strings.Join(methInfo.midwares, ", ") + ", " + } + t.P(`e.`, methInfo.apiInfo.HttpMethod, `("`, methInfo.apiInfo.LegacyPath, `", `, midArgStr, methInfo.routeFuncName, `)`) + } + t.P(` }`) + } else { + // 新的注册路由的方法 + var bmFuncName = fmt.Sprintf("Register%sBMServer", servName) + t.P(`// `, bmFuncName, ` Register the blademaster route`) + t.P(`func `, bmFuncName, `(e *bm.Engine, server `, servName, `BMServer) {`) + t.P(svcName, ` = server`) + for _, methInfo := range methList { + t.P(`e.`, methInfo.apiInfo.HttpMethod, `("`, methInfo.apiInfo.NewPath, `",`, methInfo.routeFuncName, ` )`) + } + t.P(` }`) + } +} + +func (t *bm) hasHeaderTag(md *typemap.MessageDefinition) bool { + if md.Descriptor.Field == nil { + return false + } + for _, f := range md.Descriptor.Field { + t := tag.GetMoreTags(f) + if t != nil { + st := reflect.StructTag(*t) + if st.Get("request") != "" { + return true + } + if st.Get("header") != "" { + return true + } + } + } + return false +} + +func (t *bm) generateBMInterface(file *descriptor.FileDescriptorProto, service *descriptor.ServiceDescriptorProto) int { + count := 0 + servName := naming.ServiceName(service) + t.P("// " + servName + "BMServer is the server API for " + servName + " service.") + + comments, err := t.Reg.ServiceComments(file, service) + if err == nil { + t.PrintComments(comments) + } + t.P(`type `, servName, `BMServer interface {`) + for _, method := range service.Method { + if !t.ShouldGenForMethod(file, service, method) { + continue + } + count++ + t.generateInterfaceMethod(file, service, method, comments) + t.P() + } + t.P(`}`) + return count +} + +func (t *bm) generateInterfaceMethod(file *descriptor.FileDescriptorProto, + service *descriptor.ServiceDescriptorProto, + method *descriptor.MethodDescriptorProto, + comments typemap.DefinitionComments) { + comments, err := t.Reg.MethodComments(file, service, method) + + methName := naming.MethodName(method) + outputType := t.GoTypeName(method.GetOutputType()) + inputType := t.GoTypeName(method.GetInputType()) + tags := tag.GetTagsInComment(comments.Leading) + if tag.GetTagValue("dynamic", tags) == "true" { + return + } + + if err == nil { + t.PrintComments(comments) + } + + respDynamic := tag.GetTagValue("dynamic_resp", tags) == "true" + if respDynamic { + t.P(fmt.Sprintf(` %s(ctx context.Context, req *%s) (resp interface{}, err error)`, + methName, inputType)) + } else { + t.P(fmt.Sprintf(` %s(ctx context.Context, req *%s) (resp *%s, err error)`, + methName, inputType, outputType)) + } +} diff --git a/tool/protobuf/protoc-gen-bm/generator/generator_test.go b/tool/protobuf/protoc-gen-bm/generator/generator_test.go new file mode 100644 index 000000000..7a5d0bc68 --- /dev/null +++ b/tool/protobuf/protoc-gen-bm/generator/generator_test.go @@ -0,0 +1,27 @@ +package generator + +import ( + "os" + "os/exec" + "testing" + + "github.com/golang/protobuf/proto" + plugin "github.com/golang/protobuf/protoc-gen-go/plugin" +) + +func TestGenerateParseCommandLineParamsError(t *testing.T) { + if os.Getenv("BE_CRASHER") == "1" { + g := &bm{} + g.Generate(&plugin.CodeGeneratorRequest{ + Parameter: proto.String("invalid"), + }) + return + } + cmd := exec.Command(os.Args[0], "-test.run=TestGenerateParseCommandLineParamsError") + cmd.Env = append(os.Environ(), "BE_CRASHER=1") + err := cmd.Run() + if e, ok := err.(*exec.ExitError); ok && !e.Success() { + return + } + t.Fatalf("process ran with err %v, want exit status 1", err) +} diff --git a/tool/protobuf/protoc-gen-bm/main.go b/tool/protobuf/protoc-gen-bm/main.go new file mode 100644 index 000000000..38589534c --- /dev/null +++ b/tool/protobuf/protoc-gen-bm/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/bilibili/kratos/tool/protobuf/pkg/gen" + "github.com/bilibili/kratos/tool/protobuf/pkg/generator" + bmgen "github.com/bilibili/kratos/tool/protobuf/protoc-gen-bm/generator" +) + +func main() { + versionFlag := flag.Bool("version", false, "print version and exit") + flag.Parse() + if *versionFlag { + fmt.Println(generator.Version) + os.Exit(0) + } + + g := bmgen.BmGenerator() + gen.Main(g) +} diff --git a/tool/protobuf/protoc-gen-bswagger/generator.go b/tool/protobuf/protoc-gen-bswagger/generator.go new file mode 100644 index 000000000..da606167c --- /dev/null +++ b/tool/protobuf/protoc-gen-bswagger/generator.go @@ -0,0 +1,303 @@ +package main + +import ( + "encoding/json" + "net/http" + "regexp" + "strings" + + "github.com/bilibili/kratos/tool/protobuf/pkg/gen" + "github.com/bilibili/kratos/tool/protobuf/pkg/generator" + "github.com/bilibili/kratos/tool/protobuf/pkg/naming" + "github.com/bilibili/kratos/tool/protobuf/pkg/tag" + "github.com/bilibili/kratos/tool/protobuf/pkg/typemap" + "github.com/golang/protobuf/protoc-gen-go/descriptor" + plugin "github.com/golang/protobuf/protoc-gen-go/plugin" +) + +type swaggerGen struct { + generator.Base + // defsMap will fill into swagger's definitions + // key is full qualified proto name + defsMap map[string]*typemap.MessageDefinition +} + +// NewSwaggerGenerator a swagger generator +func NewSwaggerGenerator() *swaggerGen { + return &swaggerGen{} +} + +func (t *swaggerGen) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse { + t.Setup(in) + resp := &plugin.CodeGeneratorResponse{} + for _, f := range t.GenFiles { + if len(f.Service) == 0 { + continue + } + respFile := t.generateSwagger(f) + if respFile != nil { + resp.File = append(resp.File, respFile) + } + } + return resp +} + +func (t *swaggerGen) generateSwagger(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File { + var pkg = file.GetPackage() + r := regexp.MustCompile("v(\\d+)$") + strs := r.FindStringSubmatch(pkg) + var vStr string + if len(strs) >= 2 { + vStr = strs[1] + } else { + vStr = "" + } + var swaggerObj = &swaggerObject{ + Paths: swaggerPathsObject{}, + Swagger: "2.0", + Info: swaggerInfoObject{ + Title: file.GetName(), + Version: vStr, + }, + Schemes: []string{"http", "https"}, + Consumes: []string{"application/json", "multipart/form-data"}, + Produces: []string{"application/json"}, + } + t.defsMap = map[string]*typemap.MessageDefinition{} + + out := &plugin.CodeGeneratorResponse_File{} + name := naming.GoFileName(file, ".swagger.json") + for _, svc := range file.Service { + for _, meth := range svc.Method { + if !t.ShouldGenForMethod(file, svc, meth) { + continue + } + apiInfo := t.GetHttpInfoCached(file, svc, meth) + pathItem := swaggerPathItemObject{} + + op := t.getOperationByHTTPMethod(apiInfo.HttpMethod, &pathItem) + op.Summary = apiInfo.Title + op.Description = apiInfo.Description + swaggerObj.Paths[apiInfo.Path] = pathItem + op.Tags = []string{pkg + "." + svc.GetName()} + + // request + request := t.Reg.MessageDefinition(meth.GetInputType()) + // request cannot represent by simple form + isComplexRequest := false + for _, field := range request.Descriptor.Field { + if !generator.IsScalar(field) { + isComplexRequest = true + break + } + } + if !isComplexRequest && apiInfo.HttpMethod == "GET" { + for _, field := range request.Descriptor.Field { + if !generator.IsScalar(field) { + continue + } + p := t.getQueryParameter(file, request, field) + op.Parameters = append(op.Parameters, p) + } + } else { + p := swaggerParameterObject{} + p.In = "body" + p.Required = true + p.Name = "body" + p.Schema = &swaggerSchemaObject{} + p.Schema.Ref = "#/definitions/" + meth.GetInputType() + op.Parameters = []swaggerParameterObject{p} + } + + // response + resp := swaggerResponseObject{} + resp.Description = "A successful response." + + // proto 里面的response只定义data里面的 + // 所以需要把code msg data 这一级加上 + resp.Schema.Type = "object" + resp.Schema.Properties = &swaggerSchemaObjectProperties{} + p := keyVal{Key: "code", Value: &schemaCore{Type: "integer"}} + *resp.Schema.Properties = append(*resp.Schema.Properties, p) + p = keyVal{Key: "message", Value: &schemaCore{Type: "string"}} + *resp.Schema.Properties = append(*resp.Schema.Properties, p) + p = keyVal{Key: "data", Value: schemaCore{Ref: "#/definitions/" + meth.GetOutputType()}} + *resp.Schema.Properties = append(*resp.Schema.Properties, p) + op.Responses = swaggerResponsesObject{"200": resp} + } + } + + // walk though definitions + t.walkThroughFileDefinition(file) + defs := swaggerDefinitionsObject{} + swaggerObj.Definitions = defs + for typ, msg := range t.defsMap { + def := swaggerSchemaObject{} + def.Properties = new(swaggerSchemaObjectProperties) + def.Description = strings.Trim(msg.Comments.Leading, "\n\r ") + for _, field := range msg.Descriptor.Field { + p := keyVal{Key: generator.GetFormOrJSONName(field)} + schema := t.schemaForField(file, msg, field) + if generator.GetFieldRequired(field, t.Reg, msg) { + def.Required = append(def.Required, p.Key) + } + p.Value = schema + *def.Properties = append(*def.Properties, p) + } + def.Type = "object" + defs[typ] = def + } + b, _ := json.MarshalIndent(swaggerObj, "", " ") + str := string(b) + out.Name = &name + out.Content = &str + return out +} + +func (t *swaggerGen) getOperationByHTTPMethod(httpMethod string, pathItem *swaggerPathItemObject) *swaggerOperationObject { + var op = &swaggerOperationObject{} + switch httpMethod { + case http.MethodGet: + pathItem.Get = op + case http.MethodPost: + pathItem.Post = op + case http.MethodPut: + pathItem.Put = op + case http.MethodDelete: + pathItem.Put = op + case http.MethodPatch: + pathItem.Patch = op + default: + pathItem.Get = op + } + return op +} + +func (t *swaggerGen) getQueryParameter(file *descriptor.FileDescriptorProto, + input *typemap.MessageDefinition, + field *descriptor.FieldDescriptorProto) swaggerParameterObject { + p := swaggerParameterObject{} + p.Name = generator.GetFormOrJSONName(field) + fComment, _ := t.Reg.FieldComments(input, field) + cleanComment := tag.GetCommentWithoutTag(fComment.Leading) + + p.Description = strings.Trim(strings.Join(cleanComment, "\n"), "\n\r ") + p.In = "query" + p.Required = generator.GetFieldRequired(field, t.Reg, input) + typ, isArray, format := getFieldSwaggerType(field) + if isArray { + p.Items = &swaggerItemsObject{} + p.Type = "array" + p.Items.Type = typ + p.Items.Format = format + } else { + p.Type = typ + p.Format = format + } + return p +} + +func (t *swaggerGen) schemaForField(file *descriptor.FileDescriptorProto, + msg *typemap.MessageDefinition, + field *descriptor.FieldDescriptorProto) swaggerSchemaObject { + schema := swaggerSchemaObject{} + fComment, err := t.Reg.FieldComments(msg, field) + if err != nil { + gen.Error(err, "comment not found err %+v") + } + schema.Description = strings.Trim(fComment.Leading, "\n\r ") + typ, isArray, format := getFieldSwaggerType(field) + if !generator.IsScalar(field) { + if generator.IsMap(field, t.Reg) { + schema.Type = "object" + mapMsg := t.Reg.MessageDefinition(field.GetTypeName()) + mapValueField := mapMsg.Descriptor.Field[1] + valSchema := t.schemaForField(file, mapMsg, mapValueField) + schema.AdditionalProperties = &valSchema + } else { + if isArray { + schema.Items = &swaggerItemsObject{} + schema.Type = "array" + schema.Items.Ref = "#/definitions/" + field.GetTypeName() + } else { + schema.Ref = "#/definitions/" + field.GetTypeName() + } + } + } else { + if isArray { + schema.Items = &swaggerItemsObject{} + schema.Type = "array" + schema.Items.Type = typ + schema.Items.Format = format + } else { + schema.Type = typ + schema.Format = format + } + } + return schema +} + +func (t *swaggerGen) walkThroughFileDefinition(file *descriptor.FileDescriptorProto) { + for _, svc := range file.Service { + for _, meth := range svc.Method { + shouldGen := t.ShouldGenForMethod(file, svc, meth) + if !shouldGen { + continue + } + t.walkThroughMessages(t.Reg.MessageDefinition(meth.GetOutputType())) + t.walkThroughMessages(t.Reg.MessageDefinition(meth.GetInputType())) + } + } +} + +func (t *swaggerGen) walkThroughMessages(msg *typemap.MessageDefinition) { + _, ok := t.defsMap[msg.ProtoName()] + if ok { + return + } + if !msg.Descriptor.GetOptions().GetMapEntry() { + t.defsMap[msg.ProtoName()] = msg + } + for _, field := range msg.Descriptor.Field { + if field.GetType() == descriptor.FieldDescriptorProto_TYPE_MESSAGE { + t.walkThroughMessages(t.Reg.MessageDefinition(field.GetTypeName())) + } + } +} + +func getFieldSwaggerType(field *descriptor.FieldDescriptorProto) (typeName string, isArray bool, formatName string) { + typeName = "unknown" + switch field.GetType() { + case descriptor.FieldDescriptorProto_TYPE_BOOL: + typeName = "boolean" + case descriptor.FieldDescriptorProto_TYPE_DOUBLE: + typeName = "number" + formatName = "double" + case descriptor.FieldDescriptorProto_TYPE_FLOAT: + typeName = "number" + formatName = "float" + case + descriptor.FieldDescriptorProto_TYPE_INT64, + descriptor.FieldDescriptorProto_TYPE_UINT64, + descriptor.FieldDescriptorProto_TYPE_INT32, + descriptor.FieldDescriptorProto_TYPE_FIXED64, + descriptor.FieldDescriptorProto_TYPE_FIXED32, + descriptor.FieldDescriptorProto_TYPE_ENUM, + descriptor.FieldDescriptorProto_TYPE_UINT32, + descriptor.FieldDescriptorProto_TYPE_SFIXED32, + descriptor.FieldDescriptorProto_TYPE_SFIXED64, + descriptor.FieldDescriptorProto_TYPE_SINT32, + descriptor.FieldDescriptorProto_TYPE_SINT64: + typeName = "integer" + case + descriptor.FieldDescriptorProto_TYPE_STRING, + descriptor.FieldDescriptorProto_TYPE_BYTES: + typeName = "string" + case descriptor.FieldDescriptorProto_TYPE_MESSAGE: + typeName = "object" + } + if field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED { + isArray = true + } + return +} diff --git a/tool/protobuf/protoc-gen-bswagger/main.go b/tool/protobuf/protoc-gen-bswagger/main.go new file mode 100644 index 000000000..6bedb7993 --- /dev/null +++ b/tool/protobuf/protoc-gen-bswagger/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/bilibili/kratos/tool/protobuf/pkg/gen" + "github.com/bilibili/kratos/tool/protobuf/pkg/generator" +) + +func main() { + versionFlag := flag.Bool("version", false, "print version and exit") + flag.Parse() + if *versionFlag { + fmt.Println(generator.Version) + os.Exit(0) + } + + g := NewSwaggerGenerator() + gen.Main(g) +} diff --git a/tool/protobuf/protoc-gen-bswagger/types.go b/tool/protobuf/protoc-gen-bswagger/types.go new file mode 100644 index 000000000..e6a7a866c --- /dev/null +++ b/tool/protobuf/protoc-gen-bswagger/types.go @@ -0,0 +1,218 @@ +package main + +import ( + "bytes" + "encoding/json" +) + +// http://swagger.io/specification/#infoObject +type swaggerInfoObject struct { + Title string `json:"title"` + Description string `json:"description,omitempty"` + TermsOfService string `json:"termsOfService,omitempty"` + Version string `json:"version"` + + Contact *swaggerContactObject `json:"contact,omitempty"` + License *swaggerLicenseObject `json:"license,omitempty"` +} + +// http://swagger.io/specification/#contactObject +type swaggerContactObject struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + Email string `json:"email,omitempty"` +} + +// http://swagger.io/specification/#licenseObject +type swaggerLicenseObject struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` +} + +// http://swagger.io/specification/#externalDocumentationObject +type swaggerExternalDocumentationObject struct { + Description string `json:"description,omitempty"` + URL string `json:"url,omitempty"` +} + +// http://swagger.io/specification/#swaggerObject +type swaggerObject struct { + Swagger string `json:"swagger"` + Info swaggerInfoObject `json:"info"` + Host string `json:"host,omitempty"` + BasePath string `json:"basePath,omitempty"` + Schemes []string `json:"schemes"` + Consumes []string `json:"consumes"` + Produces []string `json:"produces"` + Paths swaggerPathsObject `json:"paths"` + Definitions swaggerDefinitionsObject `json:"definitions"` + StreamDefinitions swaggerDefinitionsObject `json:"x-stream-definitions,omitempty"` + SecurityDefinitions swaggerSecurityDefinitionsObject `json:"securityDefinitions,omitempty"` + Security []swaggerSecurityRequirementObject `json:"security,omitempty"` + ExternalDocs *swaggerExternalDocumentationObject `json:"externalDocs,omitempty"` +} + +// http://swagger.io/specification/#securityDefinitionsObject +type swaggerSecurityDefinitionsObject map[string]swaggerSecuritySchemeObject + +// http://swagger.io/specification/#securitySchemeObject +type swaggerSecuritySchemeObject struct { + Type string `json:"type"` + Description string `json:"description,omitempty"` + Name string `json:"name,omitempty"` + In string `json:"in,omitempty"` + Flow string `json:"flow,omitempty"` + AuthorizationURL string `json:"authorizationUrl,omitempty"` + TokenURL string `json:"tokenUrl,omitempty"` + Scopes swaggerScopesObject `json:"scopes,omitempty"` +} + +// http://swagger.io/specification/#scopesObject +type swaggerScopesObject map[string]string + +// http://swagger.io/specification/#securityRequirementObject +type swaggerSecurityRequirementObject map[string][]string + +// http://swagger.io/specification/#pathsObject +type swaggerPathsObject map[string]swaggerPathItemObject + +// http://swagger.io/specification/#pathItemObject +type swaggerPathItemObject struct { + Get *swaggerOperationObject `json:"get,omitempty"` + Delete *swaggerOperationObject `json:"delete,omitempty"` + Post *swaggerOperationObject `json:"post,omitempty"` + Put *swaggerOperationObject `json:"put,omitempty"` + Patch *swaggerOperationObject `json:"patch,omitempty"` +} + +// http://swagger.io/specification/#operationObject +type swaggerOperationObject struct { + Summary string `json:"summary,omitempty"` + Description string `json:"description,omitempty"` + OperationID string `json:"operationId,omitempty"` + Responses swaggerResponsesObject `json:"responses"` + Parameters swaggerParametersObject `json:"parameters,omitempty"` + Tags []string `json:"tags,omitempty"` + Deprecated bool `json:"deprecated,omitempty"` + + Security *[]swaggerSecurityRequirementObject `json:"security,omitempty"` + ExternalDocs *swaggerExternalDocumentationObject `json:"externalDocs,omitempty"` +} + +type swaggerParametersObject []swaggerParameterObject + +// http://swagger.io/specification/#parameterObject +type swaggerParameterObject struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + In string `json:"in,omitempty"` + Required bool `json:"required"` + Type string `json:"type,omitempty"` + Format string `json:"format,omitempty"` + Items *swaggerItemsObject `json:"items,omitempty"` + Enum []string `json:"enum,omitempty"` + CollectionFormat string `json:"collectionFormat,omitempty"` + Default string `json:"default,omitempty"` + MinItems *int `json:"minItems,omitempty"` + + // Or you can explicitly refer to another type. If this is defined all + // other fields should be empty + Schema *swaggerSchemaObject `json:"schema,omitempty"` +} + +// core part of schema, which is common to itemsObject and schemaObject. +// http://swagger.io/specification/#itemsObject +type schemaCore struct { + Type string `json:"type,omitempty"` + Format string `json:"format,omitempty"` + Ref string `json:"$ref,omitempty"` + Example json.RawMessage `json:"example,omitempty"` + + Items *swaggerItemsObject `json:"items,omitempty"` + + // If the item is an enumeration include a list of all the *NAMES* of the + // enum values. I'm not sure how well this will work but assuming all enums + // start from 0 index it will be great. I don't think that is a good assumption. + Enum []string `json:"enum,omitempty"` + Default string `json:"default,omitempty"` +} + +type swaggerItemsObject schemaCore + +func (o *swaggerItemsObject) getType() string { + if o == nil { + return "" + } + return o.Type +} + +// http://swagger.io/specification/#responsesObject +type swaggerResponsesObject map[string]swaggerResponseObject + +// http://swagger.io/specification/#responseObject +type swaggerResponseObject struct { + Description string `json:"description"` + Schema swaggerSchemaObject `json:"schema"` +} + +type keyVal struct { + Key string + Value interface{} +} + +type swaggerSchemaObjectProperties []keyVal + +func (op swaggerSchemaObjectProperties) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + buf.WriteString("{") + for i, kv := range op { + if i != 0 { + buf.WriteString(",") + } + key, err := json.Marshal(kv.Key) + if err != nil { + return nil, err + } + buf.Write(key) + buf.WriteString(":") + val, err := json.Marshal(kv.Value) + if err != nil { + return nil, err + } + buf.Write(val) + } + + buf.WriteString("}") + return buf.Bytes(), nil +} + +// http://swagger.io/specification/#schemaObject +type swaggerSchemaObject struct { + schemaCore + // Properties can be recursively defined + Properties *swaggerSchemaObjectProperties `json:"properties,omitempty"` + AdditionalProperties *swaggerSchemaObject `json:"additionalProperties,omitempty"` + + Description string `json:"description,omitempty"` + Title string `json:"title,omitempty"` + + ExternalDocs *swaggerExternalDocumentationObject `json:"externalDocs,omitempty"` + + MultipleOf float64 `json:"multipleOf,omitempty"` + Maximum float64 `json:"maximum,omitempty"` + ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` + Minimum float64 `json:"minimum,omitempty"` + ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` + MaxLength uint64 `json:"maxLength,omitempty"` + MinLength uint64 `json:"minLength,omitempty"` + Pattern string `json:"pattern,omitempty"` + MaxItems uint64 `json:"maxItems,omitempty"` + MinItems uint64 `json:"minItems,omitempty"` + UniqueItems bool `json:"uniqueItems,omitempty"` + MaxProperties uint64 `json:"maxProperties,omitempty"` + MinProperties uint64 `json:"minProperties,omitempty"` + Required []string `json:"required,omitempty"` +} + +// http://swagger.io/specification/#definitionsObject +type swaggerDefinitionsObject map[string]swaggerSchemaObject