fix: autogen router map
This commit is contained in:
parent
91a5eb75b8
commit
83f235636e
|
@ -0,0 +1,13 @@
|
|||
# protoc-gen-coco
|
||||
|
||||
## install
|
||||
|
||||
```shell
|
||||
go install gitter.top/coco/protoc-gen-coco@latest
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
```shell
|
||||
protoc example.proto --coco_out=. --go_out=.
|
||||
```
|
|
@ -130,3 +130,23 @@ func GetCommentApiURL(method *protogen.Method) string {
|
|||
}
|
||||
return "/" + CamelCaseToJavascriptCase(method.GoName)
|
||||
}
|
||||
|
||||
func GetRPCInfoList(services *protogen.Service) []*rpcInfo {
|
||||
if len(services.Methods) == 0 {
|
||||
return nil
|
||||
}
|
||||
var list []*rpcInfo
|
||||
for _, method := range services.Methods {
|
||||
node := &rpcInfo{
|
||||
FuncName: method.GoName,
|
||||
RouterPath: GetCommentApiURL(method),
|
||||
Method: GetCommentHttpMethod(method.Comments),
|
||||
Author: GetCommentAuthor(method.Comments),
|
||||
Describe: GetCommentDescribe(method.Comments),
|
||||
ReqName: method.Input.GoIdent.GoName,
|
||||
RespName: method.Output.GoIdent.GoName,
|
||||
}
|
||||
list = append(list, node)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package controller;
|
||||
|
||||
option go_package = "internal/controller/core";
|
||||
|
||||
|
||||
// @route_group: true
|
||||
// @route_api: /api/file
|
||||
// @gen_to: ./core/file_controller.go
|
||||
service File {
|
||||
// @desc: 列表
|
||||
// @author: Young Xu
|
||||
// @method: GET
|
||||
// @api: /list
|
||||
rpc List (ListReq) returns (ListResp);
|
||||
// @desc: 上传
|
||||
// @author: Young Xu
|
||||
// @method: POST
|
||||
// @api: /upload
|
||||
rpc Upload (UploadReq) returns (UploadResp);
|
||||
// @desc: 删除
|
||||
// @author: Young Xu
|
||||
// @method: POST
|
||||
// @api: /delete
|
||||
rpc Delete (DeleteReq) returns (DeleteResp);
|
||||
// @desc: 下载
|
||||
// @author: Young Xu
|
||||
// @method: GET
|
||||
// @api: /download
|
||||
rpc Download (DownloadReq) returns (DownloadResp);
|
||||
}
|
||||
|
||||
|
||||
message ListReq {}
|
||||
|
||||
message ListResp {
|
||||
message Item {
|
||||
string filename = 1; // 文件名
|
||||
string file_size = 2; // 文件大小
|
||||
string created_at = 3; // 上传时间
|
||||
}
|
||||
repeated Item items = 1; // 列表
|
||||
}
|
||||
|
||||
message UploadReq {}
|
||||
|
||||
message UploadResp {}
|
||||
|
||||
message DeleteReq {
|
||||
string filename = 1; // 文件名
|
||||
}
|
||||
|
||||
message DeleteResp {}
|
||||
|
||||
message DownloadReq {
|
||||
// @v: required
|
||||
string f = 1; // 文件地址
|
||||
}
|
||||
|
||||
message DownloadResp {}
|
3
go.mod
3
go.mod
|
@ -3,7 +3,9 @@ module gitter.top/coco/protoc-gen-coco
|
|||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/emicklei/proto v1.11.1
|
||||
gitter.top/coco/coco v0.0.0-20230312134419-0dd59eb0e955
|
||||
gitter.top/coco/goast v0.0.0-20230318151709-875a73a463aa
|
||||
google.golang.org/protobuf v1.28.1
|
||||
)
|
||||
|
||||
|
@ -25,6 +27,7 @@ require (
|
|||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||
gitter.top/sync/proto-contrib v0.15.0 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
|
|
8
go.sum
8
go.sum
|
@ -7,6 +7,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/proto v1.11.1 h1:CBZwNVwPJvkdevxvsoCuFedF9ENiBz0saen3L9y0OTA=
|
||||
github.com/emicklei/proto v1.11.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||
|
@ -59,6 +61,12 @@ github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU
|
|||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
gitter.top/coco/coco v0.0.0-20230312134419-0dd59eb0e955 h1:dLnS34ByD6KgTiUHBPQSqhvh1yk9pR10P3So/Gz9Zo0=
|
||||
gitter.top/coco/coco v0.0.0-20230312134419-0dd59eb0e955/go.mod h1:IptVJiCXf87Xywfw26zrvXQjTjF8ZsIHUTJ8yPWSpWE=
|
||||
gitter.top/coco/goast v0.0.0-20230318151709-875a73a463aa h1:qSlQttmvI3fRWJUdZ5Ra5Prg+5OCfzrn/BIN8q+lItE=
|
||||
gitter.top/coco/goast v0.0.0-20230318151709-875a73a463aa/go.mod h1:irSK6CvEKqxRIu45LbT1GTXvwu9D94h51hK9bR6Zcoo=
|
||||
gitter.top/sync/proto-contrib v0.14.0 h1:kFnFolvQZjksvbg/XAMgHjyGWNoHHcKT/MdKJcN9KGQ=
|
||||
gitter.top/sync/proto-contrib v0.14.0/go.mod h1:ZnQxhRQHLM3Y8lKwvRZwDe2CZ7bkVH6dZz2yEVpU5zg=
|
||||
gitter.top/sync/proto-contrib v0.15.0 h1:V2vOTT1Rgw37jxLM8+IBHDPP8UtdCZAj4CqQIM4owFM=
|
||||
gitter.top/sync/proto-contrib v0.15.0/go.mod h1:ZnQxhRQHLM3Y8lKwvRZwDe2CZ7bkVH6dZz2yEVpU5zg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
|
|
94
main.go
94
main.go
|
@ -1,9 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/emicklei/proto"
|
||||
"gitter.top/sync/proto-contrib/pkg/protofmt"
|
||||
"google.golang.org/protobuf/compiler/protogen"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Coco struct{}
|
||||
|
@ -13,32 +18,65 @@ func (c *Coco) Generate(plugin *protogen.Plugin) error {
|
|||
if len(plugin.Files) == 0 {
|
||||
return nil
|
||||
}
|
||||
c.format(plugin)
|
||||
c.generateRouterMap(plugin)
|
||||
c.generateRouterImpl(plugin)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Coco) format(plugin *protogen.Plugin) {
|
||||
for _, pbFile := range plugin.Files {
|
||||
filename := pbFile.Desc.Path()
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "format pbFile %s failed: %v\n", filename, err)
|
||||
continue
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
parser := proto.NewParser(file)
|
||||
parser.Filename(filename)
|
||||
def, err := parser.Parse()
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "parse pbFile %s failed: %v\n", filename, err)
|
||||
continue
|
||||
}
|
||||
protofmt.NewFormatter(buf, " ").Format(def)
|
||||
if err := os.WriteFile(filename, buf.Bytes(), os.ModePerm); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "rewrite pbFile %s failed: %v\n", filename, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Coco) generateRouterMap(plugin *protogen.Plugin) {
|
||||
for _, pbFile := range plugin.Files {
|
||||
// service empty
|
||||
if len(pbFile.Services) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// write file header
|
||||
filename := fmt.Sprintf("autogen_router_%s.go", pbFile.GeneratedFilenamePrefix)
|
||||
g := plugin.NewGeneratedFile(filename, pbFile.GoImportPath)
|
||||
g.P("// Code generated by protoc-gen-coco. DO NOT EDIT.")
|
||||
g.P()
|
||||
g.P("package ", pbFile.GoPackageName)
|
||||
g.P()
|
||||
g.P(`import (
|
||||
"gitter.top/coco/coco/core"
|
||||
)`)
|
||||
g.P()
|
||||
|
||||
for _, service := range pbFile.Services {
|
||||
// service router group
|
||||
// is service router group
|
||||
if !IsCommentRouterGroup(pbFile.Services[0].Comments) {
|
||||
continue
|
||||
}
|
||||
if len(service.Methods) == 0 {
|
||||
continue
|
||||
}
|
||||
// write file header
|
||||
filename := fmt.Sprintf("%s/autogen_router_%s.go", filepath.Dir(pbFile.GeneratedFilenamePrefix), service.GoName)
|
||||
g := plugin.NewGeneratedFile(filename, pbFile.GoImportPath)
|
||||
g.P("// Code generated by protoc-gen-coco. DO NOT EDIT.")
|
||||
g.P("// source: ", pbFile.GeneratedFilenamePrefix, ".proto")
|
||||
g.P("// generate at: ", time.Now().Format("2006-01-02 15:04:05"))
|
||||
g.P()
|
||||
g.P("package ", pbFile.GoPackageName)
|
||||
g.P()
|
||||
g.P(`import (
|
||||
"gitter.top/coco/coco/core"
|
||||
)`)
|
||||
g.P()
|
||||
values, err := GenerateRouterMap(service)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "generate router map failed: %v", err)
|
||||
|
@ -47,9 +85,35 @@ func (c *Coco) Generate(plugin *protogen.Plugin) error {
|
|||
g.P(values)
|
||||
g.P()
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Coco) generateRouterImpl(plugin *protogen.Plugin) {
|
||||
for _, pbFile := range plugin.Files {
|
||||
// service empty
|
||||
if len(pbFile.Services) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, service := range pbFile.Services {
|
||||
// is service router group
|
||||
if !IsCommentRouterGroup(pbFile.Services[0].Comments) {
|
||||
continue
|
||||
}
|
||||
|
||||
genTo := GetCommentGenerateTo(service.Comments)
|
||||
generator := newRouterImpl(genTo, string(pbFile.GoPackageName), service.GoName, service)
|
||||
if !generator.IsFileExist() {
|
||||
if err := generator.generateNewFile(); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "generate all router failed: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := generator.generateNewRouters(); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "generate router impl failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -2,8 +2,13 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitter.top/coco/goast"
|
||||
"google.golang.org/protobuf/compiler/protogen"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
|
@ -28,7 +33,7 @@ const routerGenerateTpl = `type AutoGen{{.StructName}}Impl interface { ` + `{{ra
|
|||
{{.MethodName}}(ctx *core.Context, req *{{.InputName}}) (resp *{{.OutputName}}, err error){{end}}
|
||||
}
|
||||
var (
|
||||
AutoGen{{.StructName}} = &core.Routers{
|
||||
AutoGen{{.StructName}}RouterMap = &core.Routers{
|
||||
BaseURL: "{{.BaseURL}}",
|
||||
Apis: core.RouterMap{ {{range .Methods}}
|
||||
"{{.MethodName}}": {
|
||||
|
@ -73,3 +78,189 @@ func GenerateRouterMap(srv *protogen.Service) (string, error) {
|
|||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
type rpcInfo struct {
|
||||
FuncName string // 函数名
|
||||
RouterPath string // 路由路径
|
||||
Method string // 请求类型 POST/GET...
|
||||
Author string // 接口作者
|
||||
Describe string // 描述
|
||||
ReqName string // request name
|
||||
RespName string // response name
|
||||
}
|
||||
|
||||
type routerImpl struct {
|
||||
Filename string
|
||||
PkgName string
|
||||
SrvName string
|
||||
ModelName string // protobuf go_out package name
|
||||
Routers []*rpcInfo
|
||||
}
|
||||
|
||||
func newRouterImpl(filename, modelName, srvName string, service *protogen.Service) *routerImpl {
|
||||
if filename == "" {
|
||||
filename = "./"
|
||||
}
|
||||
pkgName := filepath.Base(filepath.Dir(filename))
|
||||
if pkgName == "" {
|
||||
realName, _ := os.Getwd()
|
||||
pkgName = fixPkgName(filepath.Base(realName))
|
||||
}
|
||||
return &routerImpl{
|
||||
ModelName: modelName,
|
||||
Filename: filename,
|
||||
SrvName: srvName,
|
||||
PkgName: pkgName,
|
||||
Routers: GetRPCInfoList(service),
|
||||
}
|
||||
}
|
||||
|
||||
// fixPkgName 修正不规范的包名
|
||||
func fixPkgName(name string) string {
|
||||
name = strings.ReplaceAll(name, "-", "_")
|
||||
name = strings.ReplaceAll(name, ".", "_")
|
||||
return name
|
||||
}
|
||||
|
||||
func (ri *routerImpl) IsFileExist() bool {
|
||||
_, err := os.Stat(ri.Filename)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return false
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "get file stat failed: %v", err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ri *routerImpl) GetFileInfo(filename string) (os.FileInfo, error) {
|
||||
return os.Stat(filename)
|
||||
}
|
||||
|
||||
const CompleteRouteGenerateAndPackageTpl = `package {{.PkgName}}
|
||||
|
||||
import "gitter.top/coco/coco/core"
|
||||
|
||||
type {{.SrvName}} struct {}
|
||||
|
||||
// IDE: {{.SrvName}} implemented {{if ne .ModelName .PkgName}}{{.ModelName}}.{{end}}{{.SrvName}}Impl interface
|
||||
var _ AutoGen{{if ne .ModelName .PkgName}}{{.ModelName}}.{{end}}{{.SrvName}}Impl = (*{{.SrvName}})(nil)
|
||||
|
||||
{{range $route := .Routers}}
|
||||
// {{$route.FuncName}} {{$route.Describe}}
|
||||
func (receiver *{{$.SrvName}}) {{$route.FuncName}}(ctx *core.Context, req *{{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.ReqName}}) (resp *{{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.RespName}}, err error) {
|
||||
resp = new({{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.RespName}})
|
||||
|
||||
// TODO impl...
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
func (ri *routerImpl) generateNewFile() error {
|
||||
t, err := template.New("generate_all_file").Parse(CompleteRouteGenerateAndPackageTpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.DefinedTemplates()
|
||||
var buf bytes.Buffer
|
||||
if err = t.Execute(&buf, ri); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(ri.Filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err = f.Write(buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ri *routerImpl) generateNewRouters() error {
|
||||
info, err := ri.GetFileInfo(ri.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
parser, err := goast.NewParser(ri.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
definer, err := parser.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !definer.ExistType(ri.SrvName) {
|
||||
tpl := `
|
||||
type {{.SrvName}} struct {}
|
||||
|
||||
// IDE: {{.SrvName}} implemented {{if ne .ModelName .PkgName}}{{.ModelName}}.{{end}}{{.SrvName}}Impl interface
|
||||
var _ AutoGen{{if ne .ModelName .PkgName}}{{.ModelName}}.{{end}}{{.SrvName}}Impl = (*{{.SrvName}})(nil)
|
||||
`
|
||||
t, err := template.New("generate_type").Parse(tpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.DefinedTemplates()
|
||||
var buf bytes.Buffer
|
||||
if err = t.Execute(&buf, ri); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(ri.Filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err = f.Write(buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var needImpls []*rpcInfo
|
||||
for _, router := range ri.Routers {
|
||||
if !definer.ExistMethod(fmt.Sprintf("%s.%s", ri.SrvName, router.FuncName)) {
|
||||
needImpls = append(needImpls, router)
|
||||
}
|
||||
}
|
||||
if len(needImpls) != 0 {
|
||||
tpl := `{{range $route := .Routers}}
|
||||
// {{$route.FuncName}} {{$route.Describe}}
|
||||
func (receiver *{{$.SrvName}}) {{$route.FuncName}}(ctx *core.Context, req *{{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.ReqName}}) (resp *{{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.RespName}}, err error) {
|
||||
resp = new({{if ne $.ModelName $.PkgName}}{{$.ModelName}}.{{end}}{{$route.RespName}})
|
||||
|
||||
// TODO impl...
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
{{end}}`
|
||||
ri.Routers = needImpls
|
||||
t, err := template.New("generate_impls").Parse(tpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.DefinedTemplates()
|
||||
var buf bytes.Buffer
|
||||
if err = t.Execute(&buf, ri); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(ri.Filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err = f.Write(buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
41
test.proto
41
test.proto
|
@ -1,41 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package v1;
|
||||
|
||||
option go_package = "./;main";
|
||||
|
||||
// @route_group: true
|
||||
// @base_url: /api/v1
|
||||
service ExampleService { // tail
|
||||
rpc ExampleCall1(ExampleMessage1) returns(ReturnType) {}
|
||||
rpc ExampleCall2(ExampleMessage2) returns(ReturnType) {}
|
||||
}
|
||||
|
||||
// @route_group: true
|
||||
// @base_url: /api/v2
|
||||
service Example1Service { // tail
|
||||
rpc ExampleCall1(ExampleMessage1) returns(ReturnType) {}
|
||||
rpc ExampleCall2(ExampleMessage2) returns(ReturnType) {}
|
||||
}
|
||||
|
||||
// ExampleMessage1 - Example Leading Comment for ExampleMessage1
|
||||
message ExampleMessage1 {
|
||||
string MyString = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
ExampleMessage2 - Example Leading Comment for ExampleMessage2
|
||||
*/
|
||||
message ExampleMessage2 {
|
||||
int32 MyInt = 1;
|
||||
// MyInt - Example trailing Comment
|
||||
message ExampleNested {
|
||||
bytes data = 1;
|
||||
}
|
||||
ExampleNested nested = 2;
|
||||
}
|
||||
|
||||
/*
|
||||
ReturnType - Empty Structure Placeholder
|
||||
*/
|
||||
message ReturnType {}
|
Loading…
Reference in New Issue