diff --git a/.github/workflows/autoapprove.yml b/.github/workflows/autoapprove.yml index ebe28c9..5bf5d9f 100644 --- a/.github/workflows/autoapprove.yml +++ b/.github/workflows/autoapprove.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: approve - uses: hmarr/auto-approve-action@v2 + uses: hmarr/auto-approve-action@v3 if: github.actor == 'vtolstov' || github.actor == 'dependabot[bot]' id: approve with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8002e9..9603352 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,13 +10,13 @@ jobs: runs-on: ubuntu-latest steps: - name: setup - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: - go-version: 1.16 + go-version: 1.17 - name: checkout uses: actions/checkout@v3 - name: cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} @@ -34,7 +34,7 @@ jobs: - name: checkout uses: actions/checkout@v3 - name: lint - uses: golangci/golangci-lint-action@v3.1.0 + uses: golangci/golangci-lint-action@v3.4.0 continue-on-error: true with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9b4d428..2f6c6de 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -45,12 +45,12 @@ jobs: - name: checkout uses: actions/checkout@v3 - name: setup - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: - go-version: 1.16 + go-version: 1.17 # Initializes the CodeQL tools for scanning. - name: init - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -61,7 +61,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -75,4 +75,4 @@ jobs: # make release - name: analyze - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 3681028..f41c1c0 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -15,7 +15,7 @@ jobs: steps: - name: metadata id: metadata - uses: dependabot/fetch-metadata@v1.3.0 + uses: dependabot/fetch-metadata@v1.3.6 with: github-token: "${{ secrets.TOKEN }}" - name: merge diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ff10c46..f313ebe 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -10,13 +10,13 @@ jobs: runs-on: ubuntu-latest steps: - name: setup - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: - go-version: 1.16 + go-version: 1.17 - name: checkout uses: actions/checkout@v3 - name: cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} @@ -34,7 +34,7 @@ jobs: - name: checkout uses: actions/checkout@v3 - name: lint - uses: golangci/golangci-lint-action@v3.1.0 + uses: golangci/golangci-lint-action@v3.4.0 continue-on-error: true with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. diff --git a/chi.go b/chi.go index b0b96a6..464a135 100644 --- a/chi.go +++ b/chi.go @@ -4,9 +4,7 @@ import ( "google.golang.org/protobuf/compiler/protogen" ) -var ( - chiPackageFiles map[protogen.GoPackageName]struct{} -) +var chiPackageFiles map[protogen.GoPackageName]struct{} func (g *Generator) chiGenerate(component string, plugin *protogen.Plugin) error { chiPackageFiles = make(map[protogen.GoPackageName]struct{}) @@ -43,7 +41,6 @@ func (g *Generator) chiGenerate(component string, plugin *protogen.Plugin) error gfile.Import(stringsPackage) gfile.Import(chiPackage) gfile.Import(chiMiddlewarePackage) - gfile.Import(microApiPackage) gfile.P("type routeKey struct{}") @@ -52,17 +49,17 @@ func (g *Generator) chiGenerate(component string, plugin *protogen.Plugin) error gfile.P("return value, ok") gfile.P("}") gfile.P() - gfile.P("func RegisterHandlers(r *", chiPackage.Ident("Mux"), ", h interface{}, eps []*", microApiPackage.Ident("Endpoint"), ") error {") + gfile.P("func RegisterHandlers(r *", chiPackage.Ident("Mux"), ", h interface{}, eps []", microServerHttpPackage.Ident("EndpointMetadata"), ") error {") gfile.P("v := ", reflectPackage.Ident("ValueOf"), "(h)") gfile.P("if v.NumMethod() < 1 {") gfile.P(`return `, fmtPackage.Ident("Errorf"), `("handler has no methods: %T", h)`) gfile.P("}") gfile.P("for _, ep := range eps {") gfile.P(`idx := `, stringsPackage.Ident("Index"), `(ep.Name, ".")`) - gfile.P("if idx < 1 || len(ep.Name) <= idx {") - gfile.P(`return `, fmtPackage.Ident("Errorf"), `("invalid `, microApiPackage.Ident("Endpoint"), ` name: %s", ep.Name)`) + gfile.P(`if idx < 1 || len(ep.Name) <= idx {`) + gfile.P(`return `, fmtPackage.Ident("Errorf"), `("invalid endpoint name: %s", ep.Name)`) gfile.P("}") - gfile.P("name := ep.Name[idx+1:]") + gfile.P(`name := ep.Name[idx+1:]`) gfile.P("m := v.MethodByName(name)") gfile.P("if !m.IsValid() || m.IsZero() {") gfile.P(`return `, fmtPackage.Ident("Errorf"), `("invalid handler, method %s not found", name)`) @@ -71,9 +68,7 @@ func (g *Generator) chiGenerate(component string, plugin *protogen.Plugin) error gfile.P("if !ok {") gfile.P(`return `, fmtPackage.Ident("Errorf"), `("invalid handler: %#+v", m.Interface())`) gfile.P("}") - gfile.P("for _, method := range ep.Method {") - gfile.P("r.With(", chiMiddlewarePackage.Ident("WithValue"), "(routeKey{}, ep.Name)).MethodFunc(method, ep.Path[0], rh)") - gfile.P("}") + gfile.P("r.With(", chiMiddlewarePackage.Ident("WithValue"), `(routeKey{}, ep.Name)).MethodFunc(ep.Method, ep.Path, rh)`) gfile.P("}") gfile.P("return nil") gfile.P("}") diff --git a/example/example.proto b/example/example.proto index 14fce3f..06ded90 100644 --- a/example/example.proto +++ b/example/example.proto @@ -26,7 +26,7 @@ service Example { } }; option (micro.api.http) = { post: "/v1/example/call/{name}"; body: "*"; }; - option (micro.api.micro_method) = { timeout: 5; }; + option (micro.api.micro_method) = { timeout: "5s"; }; }; }; diff --git a/go.mod b/go.mod index 4952a11..00bca53 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.16 require ( github.com/fatih/structtag v1.2.0 - go.unistack.org/micro-proto/v3 v3.2.7 + go.unistack.org/micro-proto/v3 v3.3.1 golang.org/x/tools v0.1.9 - google.golang.org/protobuf v1.27.1 + google.golang.org/protobuf v1.28.1 ) diff --git a/go.sum b/go.sum index 8189285..542f737 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/gnostic v0.6.6 h1:MVSM2r2j9aRUvYNym66JGW96Ddd5MN4sTi59yktb6yk= -github.com/google/gnostic v0.6.6/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -68,8 +68,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.unistack.org/micro-proto/v3 v3.2.7 h1:zG6d69kHc+oij2lwQ3AfrCgdjiEVRG2A7TlsxjusWs4= -go.unistack.org/micro-proto/v3 v3.2.7/go.mod h1:ZltVWNECD5yK+40+OCONzGw4OtmSdTpVi8/KFgo9dqM= +go.unistack.org/micro-proto/v3 v3.3.1 h1:nQ0MtWvP2G3QrpOgawVOPhpZZYkq6umTGDqs8FxJYIo= +go.unistack.org/micro-proto/v3 v3.3.1/go.mod h1:cwRyv8uInM2I7EbU7O8Fx2Ls3N90Uw9UCCcq4olOdfE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -151,8 +151,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/gorilla.go b/gorilla.go index 6147cdd..ff7b2dc 100644 --- a/gorilla.go +++ b/gorilla.go @@ -4,9 +4,7 @@ import ( "google.golang.org/protobuf/compiler/protogen" ) -var ( - gorillaPackageFiles map[protogen.GoPackageName]struct{} -) +var gorillaPackageFiles map[protogen.GoPackageName]struct{} func (g *Generator) gorillaGenerate(component string, plugin *protogen.Plugin) error { gorillaPackageFiles = make(map[protogen.GoPackageName]struct{}) @@ -40,20 +38,19 @@ func (g *Generator) gorillaGenerate(component string, plugin *protogen.Plugin) e gfile.Import(httpPackage) gfile.Import(reflectPackage) gfile.Import(stringsPackage) - gfile.Import(microApiPackage) gfile.Import(gorillaMuxPackage) - gfile.P("func RegisterHandlers(r *", gorillaMuxPackage.Ident("Router"), ", h interface{}, eps []*", microApiPackage.Ident("Endpoint"), ") error {") + gfile.P("func RegisterHandlers(r *", gorillaMuxPackage.Ident("Router"), ", h interface{}, eps []", microServerHttpPackage.Ident("EndpointMetadata"), ") error {") gfile.P("v := ", reflectPackage.Ident("ValueOf"), "(h)") gfile.P("if v.NumMethod() < 1 {") gfile.P(`return `, fmtPackage.Ident("Errorf"), `("handler has no methods: %T", h)`) gfile.P("}") gfile.P("for _, ep := range eps {") gfile.P(`idx := `, stringsPackage.Ident("Index"), `(ep.Name, ".")`) - gfile.P("if idx < 1 || len(ep.Name) <= idx {") - gfile.P(`return `, fmtPackage.Ident("Errorf"), `("invalid `, microApiPackage.Ident("Endpoint"), ` name: %s", ep.Name)`) + gfile.P(`if idx < 1 || len(ep.Name) <= idx {`) + gfile.P(`return `, fmtPackage.Ident("Errorf"), `("invalid endpoint name: %s", ep.Name)`) gfile.P("}") - gfile.P("name := ep.Name[idx+1:]") + gfile.P(`name := ep.Name[idx+1:]`) gfile.P("m := v.MethodByName(name)") gfile.P("if !m.IsValid() || m.IsZero() {") gfile.P(`return `, fmtPackage.Ident("Errorf"), `("invalid handler, method %s not found", name)`) @@ -62,7 +59,7 @@ func (g *Generator) gorillaGenerate(component string, plugin *protogen.Plugin) e gfile.P("if !ok {") gfile.P(`return `, fmtPackage.Ident("Errorf"), `("invalid handler: %#+v", m.Interface())`) gfile.P("}") - gfile.P("r.HandleFunc(ep.Path[0], rh).Methods(ep.Method...).Name(ep.Name)") + gfile.P(`r.HandleFunc(ep.Path, rh).Methods(ep.Method).Name(ep.Name)`) gfile.P("}") gfile.P("return nil") gfile.P("}") diff --git a/http.go b/http.go index ef55026..a9c0335 100644 --- a/http.go +++ b/http.go @@ -28,7 +28,6 @@ func (g *Generator) httpGenerate(component string, plugin *protogen.Plugin, genC gfile.P() gfile.Import(contextPackage) - gfile.Import(microApiPackage) if genClient { gfile.Import(microClientPackage) @@ -40,13 +39,13 @@ func (g *Generator) httpGenerate(component string, plugin *protogen.Plugin, genC for _, service := range file.Services { if genClient { - generateServiceClient(gfile, service) - generateServiceClientMethods(gfile, service, true) + g.generateServiceClient(gfile, service) + g.generateServiceClientMethods(gfile, service, component) } if genServer { generateServiceServer(gfile, service) - generateServiceServerMethods(gfile, service) - generateServiceRegister(gfile, service) + g.generateServiceServerMethods(gfile, service) + g.generateServiceRegister(gfile, service, component) } } } diff --git a/main.go b/main.go index 710bced..c816eae 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ var ( flagComponents = flagSet.String("components", "micro|rpc|http|client|server|openapiv3", "specify components to generate") flagTagPath = flagSet.String("tag_path", "", "tag rewriting dir") flagOpenapiFile = flagSet.String("openapi_file", "apidocs.swagger.json", "openapi file name") + flagReflection = flagSet.Bool("reflection", false, "enable server reflection support") flagHelp = flagSet.Bool("help", false, "display help message") ) @@ -45,17 +46,21 @@ type Generator struct { fieldaligment bool tagPath string openapiFile string + reflection bool + plugin *protogen.Plugin } func (g *Generator) Generate(plugin *protogen.Plugin) error { var err error + g.plugin = plugin g.standalone = *flagStandalone g.debug = *flagDebug g.components = *flagComponents g.fieldaligment = *flagFieldaligment g.tagPath = *flagTagPath g.openapiFile = *flagOpenapiFile + g.reflection = *flagReflection plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) var genClient bool @@ -87,8 +92,8 @@ func (g *Generator) Generate(plugin *protogen.Plugin) error { err = g.microGenerate(component, plugin, genClient, genServer) case "http": err = g.httpGenerate(component, plugin, genClient, genServer) - case "grpc", "rpc": - err = g.rpcGenerate("rpc", plugin, genClient, genServer) + case "grpc", "drpc", "rpc": + err = g.rpcGenerate(component, plugin, genClient, genServer) case "gorilla": err = g.gorillaGenerate(component, plugin) case "chi": diff --git a/micro.go b/micro.go index 0daf737..6c33b23 100644 --- a/micro.go +++ b/micro.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + "google.golang.org/protobuf/compiler/protogen" ) @@ -22,27 +24,30 @@ func (g *Generator) microGenerate(component string, plugin *protogen.Plugin, gen gfile := plugin.NewGeneratedFile(gname, path) gfile.P("// Code generated by protoc-gen-go-micro. DO NOT EDIT.") - gfile.P("// protoc-gen-go-micro version: " + versionComment) - gfile.P("// source: ", file.Proto.GetName()) + gfile.P("// versions:") + gfile.P("// - protoc-gen-go-micro " + versionComment) + gfile.P("// - protoc ", protocVersion(plugin)) + gfile.P("// source: ", file.Desc.Path()) gfile.P() gfile.P("package ", file.GoPackageName) gfile.P() gfile.Import(contextPackage) - gfile.Import(microApiPackage) + if genClient { gfile.Import(microClientPackage) } // generate services for _, service := range file.Services { - generateServiceEndpoints(gfile, service) + g.generateServiceName(gfile, service) + g.generateServiceEndpoints(gfile, service) if genClient { - generateServiceClientInterface(gfile, service) - generateServiceClientStreamInterface(gfile, service) + g.generateServiceClientInterface(gfile, service) + g.generateServiceClientStreamInterface(gfile, service) } if genServer { - generateServiceServerInterface(gfile, service) - generateServiceServerStreamInterface(gfile, service) + g.generateServiceServerInterface(gfile, service) + g.generateServiceServerStreamInterface(gfile, service) } } @@ -50,3 +55,15 @@ func (g *Generator) microGenerate(component string, plugin *protogen.Plugin, gen return nil } + +func protocVersion(plugin *protogen.Plugin) string { + v := plugin.Request.GetCompilerVersion() + if v == nil { + return "(unknown)" + } + var suffix string + if s := v.GetSuffix(); s != "" { + suffix = "-" + s + } + return fmt.Sprintf("v%d.%d.%d%s", v.GetMajor(), v.GetMinor(), v.GetPatch(), suffix) +} diff --git a/openapiv3.go b/openapiv3.go index 00a3ed8..7723d5a 100644 --- a/openapiv3.go +++ b/openapiv3.go @@ -653,49 +653,54 @@ func (g *openapiv3Generator) buildOperationV3( opt := eopt.(*v3.Operation) if r := opt.Responses; r != nil { responses = r - } - if ref := responses.Default.GetReference(); ref != nil && ref.GetXRef() != "" { - xref := strings.TrimPrefix(ref.GetXRef(), ".") - description := "Default" - if strings.Contains(xref, "micro.errors.Error") { - description += " Error" - } - desc, err := protofiles.FindDescriptorByName(protoreflect.FullName(xref)) - if err != nil { - log.Printf("unknown ref type %s err %v", xref, err) - } else { - responses.Default.Oneof = &v3.ResponseOrReference_Response{ - Response: &v3.Response{ - Description: description, - Content: g.responseContentForMessage(&protogen.Message{ - Desc: desc.(protoreflect.MessageDescriptor), - }), - }, - } - } - } - for _, rref := range responses.GetResponseOrReference() { - if ref := rref.Value.GetReference(); ref != nil && ref.GetXRef() != "" { - xref := strings.TrimPrefix(ref.GetXRef(), ".") - description := "Default" - if strings.Contains(xref, "micro.errors.Error") { - description += " Error" - } - desc, err := protofiles.FindDescriptorByName(protoreflect.FullName(xref)) - if err != nil { - log.Printf("unknown ref type %s err %v", xref, err) - } else { - responses.Default.Oneof = &v3.ResponseOrReference_Response{ - Response: &v3.Response{ - Description: description, - Content: g.responseContentForMessage(&protogen.Message{ - Desc: desc.(protoreflect.MessageDescriptor), - }), - }, + rd := responses.Default + if rd != nil { + if ref := rd.GetReference(); ref != nil && ref.GetXRef() != "" { + xref := strings.TrimPrefix(ref.GetXRef(), ".") + description := "Default" + if strings.Contains(xref, "micro.errors.Error") { + description += " Error" + } + desc, err := protofiles.FindDescriptorByName(protoreflect.FullName(xref)) + if err != nil { + log.Printf("unknown ref type %s err %v", xref, err) + } else { + responses.Default.Oneof = &v3.ResponseOrReference_Response{ + Response: &v3.Response{ + Description: description, + Content: g.responseContentForMessage(&protogen.Message{ + Desc: desc.(protoreflect.MessageDescriptor), + }), + }, + } } } } + for _, rref := range responses.GetResponseOrReference() { + if ref := rref.Value.GetReference(); ref != nil && ref.GetXRef() != "" { + xref := strings.TrimPrefix(ref.GetXRef(), ".") + description := "Default" + if strings.Contains(xref, "micro.errors.Error") { + description += " Error" + } + desc, err := protofiles.FindDescriptorByName(protoreflect.FullName(xref)) + if err != nil { + log.Printf("unknown ref type %s err %v", xref, err) + } else { + responses.Default.Oneof = &v3.ResponseOrReference_Response{ + Response: &v3.Response{ + Description: description, + Content: g.responseContentForMessage(&protogen.Message{ + Desc: desc.(protoreflect.MessageDescriptor), + }), + }, + } + } + } + } + } else { + responses = &v3.Responses{} } } else { responses = &v3.Responses{} diff --git a/rpc.go b/rpc.go index 157114c..2160468 100644 --- a/rpc.go +++ b/rpc.go @@ -28,7 +28,7 @@ func (g *Generator) rpcGenerate(component string, plugin *protogen.Plugin, genCl gfile.P() gfile.Import(contextPackage) - gfile.Import(microApiPackage) + if genClient { gfile.Import(microClientPackage) } @@ -37,13 +37,16 @@ func (g *Generator) rpcGenerate(component string, plugin *protogen.Plugin, genCl } for _, service := range file.Services { if genClient { - generateServiceClient(gfile, service) - generateServiceClientMethods(gfile, service, false) + g.generateServiceClient(gfile, service) + g.generateServiceClientMethods(gfile, service, component) } if genServer { generateServiceServer(gfile, service) - generateServiceServerMethods(gfile, service) - generateServiceRegister(gfile, service) + g.generateServiceServerMethods(gfile, service) + g.generateServiceRegister(gfile, service, component) + } + if component == "grpc" && g.reflection { + g.generateServiceDesc(gfile, file, service) } } } diff --git a/util.go b/util.go index 3dc1e63..645b7da 100644 --- a/util.go +++ b/util.go @@ -2,8 +2,11 @@ package main import ( "fmt" + "log" "net/http" + "strconv" "strings" + "time" api_options "go.unistack.org/micro-proto/v3/api" v2 "go.unistack.org/micro-proto/v3/openapiv2" @@ -28,7 +31,7 @@ func unexport(s string) string { return strings.ToLower(s[:1]) + s[1:] } -func generateServiceClient(gfile *protogen.GeneratedFile, service *protogen.Service) { +func (g *Generator) generateServiceClient(gfile *protogen.GeneratedFile, service *protogen.Service) { serviceName := service.GoName // if rule, ok := getMicroApiService(service); ok { // gfile.P("// client wrappers ", strings.Join(rule.ClientWrappers, ", ")) @@ -44,13 +47,16 @@ func generateServiceClient(gfile *protogen.GeneratedFile, service *protogen.Serv gfile.P() } -func generateServiceClientMethods(gfile *protogen.GeneratedFile, service *protogen.Service, http bool) { +func (g *Generator) generateServiceClientMethods(gfile *protogen.GeneratedFile, service *protogen.Service, component string) { serviceName := service.GoName for _, method := range service.Methods { methodName := fmt.Sprintf("%s.%s", serviceName, method.GoName) - generateClientFuncSignature(gfile, serviceName, method) + if component == "drpc" { + methodName = fmt.Sprintf("%s.%s", method.Parent.Desc.FullName(), method.Desc.Name()) + } + g.generateClientFuncSignature(gfile, serviceName, method) - if http && method.Desc.Options() != nil { + if component == "http" && method.Desc.Options() != nil { if proto.HasExtension(method.Desc.Options(), v2.E_Openapiv2Operation) { opts := proto.GetExtension(method.Desc.Options(), v2.E_Openapiv2Operation) if opts != nil { @@ -61,20 +67,25 @@ func generateServiceClientMethods(gfile *protogen.GeneratedFile, service *protog gfile.P("errmap := make(map[string]interface{}, ", len(r.Responses.ResponseCode), ")") for _, rsp := range r.Responses.ResponseCode { if schema := rsp.Value.GetJsonReference(); schema != nil { - ref := schema.XRef - if strings.HasPrefix(ref, "."+string(service.Desc.ParentFile().Package())+".") { - ref = strings.TrimPrefix(ref, "."+string(service.Desc.ParentFile().Package())+".") + xref := schema.XRef + if strings.HasPrefix(xref, "."+string(service.Desc.ParentFile().Package())+".") { + xref = strings.TrimPrefix(xref, "."+string(service.Desc.ParentFile().Package())+".") } - if ref[0] == '.' { - ref = ref[1:] + if xref[0] == '.' { + xref = xref[1:] } - switch ref { + switch xref { case "micro.codec.Frame": gfile.P(`errmap["`, rsp.Name, `"] = &`, microCodecPackage.Ident("Frame"), "{}") case "micro.errors.Error": gfile.P(`errmap["`, rsp.Name, `"] = &`, microErrorsPackage.Ident("Error"), "{}") default: - gfile.P(`errmap["`, rsp.Name, `"] = &`, ref, "{}") + ident, err := g.getGoIdentByXref(strings.TrimPrefix(schema.XRef, ".")) + if err != nil { + log.Printf("cant find message by ref %s\n", schema.XRef) + continue + } + gfile.P(`errmap["`, rsp.Name, `"] = &`, gfile.QualifiedGoIdent(ident), "{}") } } } @@ -97,20 +108,25 @@ func generateServiceClientMethods(gfile *protogen.GeneratedFile, service *protog gfile.P("errmap := make(map[string]interface{}, ", len(resps), ")") for _, rsp := range resps { if schema := rsp.Value.GetReference(); schema != nil { - ref := schema.XRef - if strings.HasPrefix(ref, "."+string(service.Desc.ParentFile().Package())+".") { - ref = strings.TrimPrefix(ref, "."+string(service.Desc.ParentFile().Package())+".") + xref := schema.XRef + if strings.HasPrefix(xref, "."+string(service.Desc.ParentFile().Package())+".") { + xref = strings.TrimPrefix(xref, "."+string(service.Desc.ParentFile().Package())+".") } - if ref[0] == '.' { - ref = ref[1:] + if xref[0] == '.' { + xref = xref[1:] } - switch ref { + switch xref { case "micro.codec.Frame": gfile.P(`errmap["`, rsp.Name, `"] = &`, microCodecPackage.Ident("Frame"), "{}") case "micro.errors.Error": gfile.P(`errmap["`, rsp.Name, `"] = &`, microErrorsPackage.Ident("Error"), "{}") default: - gfile.P(`errmap["`, rsp.Name, `"] = &`, ref, "{}") + ident, err := g.getGoIdentByXref(strings.TrimPrefix(schema.XRef, ".")) + if err != nil { + log.Printf("cant find message by ref %s\n", schema.XRef) + continue + } + gfile.P(`errmap["`, rsp.Name, `"] = &`, gfile.QualifiedGoIdent(ident), "{}") } } } @@ -199,8 +215,14 @@ func generateServiceClientMethods(gfile *protogen.GeneratedFile, service *protog } if rule, ok := getMicroApiMethod(method); ok { - if rule.Timeout > 0 { - gfile.P("opts = append(opts, ", microClientPackage.Ident("WithRequestTimeout"), "(", timePackage.Ident("Second"), "*", rule.Timeout, "))") + if rule.Timeout != "" { + td, err := time.ParseDuration(rule.Timeout) + if err != nil { + log.Printf("parse duration error %s\n", err.Error()) + } else { + gfile.P("td := ", timePackage.Ident("Duration"), "(", td.Nanoseconds(), ")") + gfile.P("opts = append(opts, ", microClientPackage.Ident("WithRequestTimeout"), "(td))") + } } } @@ -273,6 +295,11 @@ func generateServiceClientMethods(gfile *protogen.GeneratedFile, service *protog gfile.P() if method.Desc.IsStreamingClient() { + gfile.P("func (s *", unexport(serviceName), "Client", method.GoName, ") Header() ", microMetadataPackage.Ident("Metadata"), "{") + gfile.P("return s.stream.Response().Header()") + gfile.P("}") + gfile.P() + gfile.P("func (s *", unexport(serviceName), "Client", method.GoName, ") Send(msg *", gfile.QualifiedGoIdent(method.Input.GoIdent), ") error {") gfile.P("return s.stream.Send(msg)") gfile.P("}") @@ -300,15 +327,21 @@ func generateServiceServer(gfile *protogen.GeneratedFile, service *protogen.Serv gfile.P() } -func generateServiceServerMethods(gfile *protogen.GeneratedFile, service *protogen.Service) { +func (g *Generator) generateServiceServerMethods(gfile *protogen.GeneratedFile, service *protogen.Service) { serviceName := service.GoName for _, method := range service.Methods { generateServerFuncSignature(gfile, serviceName, method, true) if rule, ok := getMicroApiMethod(method); ok { - if rule.Timeout > 0 { - gfile.P("var cancel ", contextPackage.Ident("CancelFunc")) - gfile.P("ctx, cancel = ", contextPackage.Ident("WithTimeout"), "(ctx, ", timePackage.Ident("Second"), "*", rule.Timeout, ")") - gfile.P("defer cancel()") + if rule.Timeout != "" { + td, err := time.ParseDuration(rule.Timeout) + if err != nil { + log.Printf("parse duration error %s\n", err.Error()) + } else { + gfile.P("var cancel ", contextPackage.Ident("CancelFunc")) + gfile.P("td := ", timePackage.Ident("Duration"), "(", td.Nanoseconds(), ")") + gfile.P("ctx, cancel = ", contextPackage.Ident("WithTimeout"), "(ctx, ", "td", ")") + gfile.P("defer cancel()") + } } } if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { @@ -444,7 +477,7 @@ func generateServiceServerMethods(gfile *protogen.GeneratedFile, service *protog } } -func generateServiceRegister(gfile *protogen.GeneratedFile, service *protogen.Service) { +func (g *Generator) generateServiceRegister(gfile *protogen.GeneratedFile, service *protogen.Service, component string) { serviceName := service.GoName gfile.P("func Register", serviceName, "Server(s ", microServerPackage.Ident("Server"), ", sh ", serviceName, "Server, opts ...", microServerPackage.Ident("HandlerOption"), ") error {") gfile.P("type ", unexport(serviceName), " interface {") @@ -457,9 +490,9 @@ func generateServiceRegister(gfile *protogen.GeneratedFile, service *protogen.Se gfile.P("}") gfile.P("h := &", unexport(serviceName), "Server{sh}") gfile.P("var nopts []", microServerPackage.Ident("HandlerOption")) - gfile.P("for _, endpoint := range ", serviceName, "Endpoints {") - gfile.P("nopts = append(nopts, ", microApiPackage.Ident("WithEndpoint"), "(&endpoint))") - gfile.P("}") + if component == "http" { + gfile.P("nopts = append(nopts, ", microServerHttpPackage.Ident("HandlerEndpoints"), "(", serviceName, "ServerEndpoints))") + } gfile.P("return s.Handle(s.NewHandler(&", serviceName, "{h}, append(nopts, opts...)...))") gfile.P("}") } @@ -508,7 +541,7 @@ func generateServerSignature(gfile *protogen.GeneratedFile, serviceName string, gfile.P(args...) } -func generateClientFuncSignature(gfile *protogen.GeneratedFile, serviceName string, method *protogen.Method) { +func (g *Generator) generateClientFuncSignature(gfile *protogen.GeneratedFile, serviceName string, method *protogen.Method) { args := append([]interface{}{}, "func (c *", unexport(serviceName), @@ -547,7 +580,7 @@ func generateClientSignature(gfile *protogen.GeneratedFile, serviceName string, gfile.P(args...) } -func generateServiceClientInterface(gfile *protogen.GeneratedFile, service *protogen.Service) { +func (g *Generator) generateServiceClientInterface(gfile *protogen.GeneratedFile, service *protogen.Service) { serviceName := service.GoName gfile.P("type ", serviceName, "Client interface {") for _, method := range service.Methods { @@ -557,7 +590,7 @@ func generateServiceClientInterface(gfile *protogen.GeneratedFile, service *prot gfile.P() } -func generateServiceServerInterface(gfile *protogen.GeneratedFile, service *protogen.Service) { +func (g *Generator) generateServiceServerInterface(gfile *protogen.GeneratedFile, service *protogen.Service) { serviceName := service.GoName gfile.P("type ", serviceName, "Server interface {") for _, method := range service.Methods { @@ -567,7 +600,7 @@ func generateServiceServerInterface(gfile *protogen.GeneratedFile, service *prot gfile.P() } -func generateServiceClientStreamInterface(gfile *protogen.GeneratedFile, service *protogen.Service) { +func (g *Generator) generateServiceClientStreamInterface(gfile *protogen.GeneratedFile, service *protogen.Service) { serviceName := service.GoName for _, method := range service.Methods { if !method.Desc.IsStreamingClient() && !method.Desc.IsStreamingServer() { @@ -584,6 +617,7 @@ func generateServiceClientStreamInterface(gfile *protogen.GeneratedFile, service } gfile.P("Close() error") if method.Desc.IsStreamingClient() { + gfile.P("Header() ", microMetadataPackage.Ident("Metadata")) gfile.P("Send(msg *", gfile.QualifiedGoIdent(method.Input.GoIdent), ") error") } if method.Desc.IsStreamingServer() { @@ -594,7 +628,7 @@ func generateServiceClientStreamInterface(gfile *protogen.GeneratedFile, service } } -func generateServiceServerStreamInterface(gfile *protogen.GeneratedFile, service *protogen.Service) { +func (g *Generator) generateServiceServerStreamInterface(gfile *protogen.GeneratedFile, service *protogen.Service) { serviceName := service.GoName for _, method := range service.Methods { if !method.Desc.IsStreamingClient() && !method.Desc.IsStreamingServer() { @@ -621,35 +655,6 @@ func generateServiceServerStreamInterface(gfile *protogen.GeneratedFile, service } } -func generateServiceEndpoints(gfile *protogen.GeneratedFile, service *protogen.Service) { - serviceName := service.GoName - gfile.P("var (") - gfile.P(serviceName, "Name", "=", `"`, serviceName, `"`) - gfile.P() - gfile.P(serviceName, "Endpoints", "=", "[]", microApiPackage.Ident("Endpoint"), "{") - for _, method := range service.Methods { - if method.Desc.Options() == nil { - continue - } - if proto.HasExtension(method.Desc.Options(), api_options.E_Http) { - endpoints, streaming := generateEndpoints(method) - for _, endpoint := range endpoints { - gfile.P("{") - generateEndpoint(gfile, serviceName, method.GoName, endpoint, streaming) - gfile.P("},") - } - } - } - gfile.P("}") - gfile.P(")") - gfile.P() - - gfile.P("func New", serviceName, "Endpoints()", "[]", microApiPackage.Ident("Endpoint"), "{") - gfile.P("return ", serviceName, "Endpoints") - gfile.P("}") - gfile.P() -} - func generateEndpoints(method *protogen.Method) ([]*api_options.HttpRule, bool) { if method.Desc.Options() == nil { return nil, false @@ -755,3 +760,108 @@ func generateEndpoint(gfile *protogen.GeneratedFile, serviceName string, methodN } gfile.P(`Handler: "rpc",`) } + +func (g *Generator) getGoIdentByXref(xref string) (protogen.GoIdent, error) { + idx := strings.LastIndex(xref, ".") + pkg := xref[:idx] + msg := xref[idx+1:] + for _, file := range g.plugin.Files { + if strings.Compare(pkg, *(file.Proto.Package)) != 0 { + continue + } + if ident, err := getGoIdentByMessage(file.Messages, msg); err == nil { + return ident, nil + } + } + return protogen.GoIdent{}, fmt.Errorf("not found") +} + +func getGoIdentByMessage(messages []*protogen.Message, msg string) (protogen.GoIdent, error) { + for _, message := range messages { + if strings.Compare(msg, message.GoIdent.GoName) == 0 { + return message.GoIdent, nil + } + if len(message.Messages) > 0 { + if ident, err := getGoIdentByMessage(message.Messages, msg); err == nil { + return ident, nil + } + } + } + return protogen.GoIdent{}, fmt.Errorf("not found") +} + +func (g *Generator) generateServiceDesc(gfile *protogen.GeneratedFile, file *protogen.File, service *protogen.Service) { + serviceName := service.GoName + + gfile.P("// ", serviceName, "_ServiceDesc", " is the ", grpcPackage.Ident("ServiceDesc"), " for ", serviceName, " service.") + gfile.P("// It's only intended for direct use with ", grpcPackage.Ident("RegisterService"), ",") + gfile.P("// and not to be introspected or modified (even as a copy)") + gfile.P("var ", serviceName, "_ServiceDesc", " = ", grpcPackage.Ident("ServiceDesc"), " {") + gfile.P("ServiceName: ", strconv.Quote(string(service.Desc.FullName())), ",") + gfile.P("HandlerType: (*", serviceName, "Server)(nil),") + gfile.P("Methods: []", grpcPackage.Ident("MethodDesc"), "{") + for _, method := range service.Methods { + if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { + continue + } + gfile.P("{") + gfile.P("MethodName: ", strconv.Quote(string(method.Desc.Name())), ",") + gfile.P("Handler: ", method.GoName, ",") + gfile.P("},") + } + gfile.P("},") + gfile.P("Streams: []", grpcPackage.Ident("StreamDesc"), "{") + for _, method := range service.Methods { + if !method.Desc.IsStreamingClient() && !method.Desc.IsStreamingServer() { + continue + } + gfile.P("{") + gfile.P("StreamName: ", strconv.Quote(string(method.Desc.Name())), ",") + gfile.P("Handler: ", method.GoName, ",") + if method.Desc.IsStreamingServer() { + gfile.P("ServerStreams: true,") + } + if method.Desc.IsStreamingClient() { + gfile.P("ClientStreams: true,") + } + gfile.P("},") + } + gfile.P("},") + gfile.P("Metadata: \"", file.Desc.Path(), "\",") + gfile.P("}") + gfile.P() +} + +func (g *Generator) generateServiceName(gfile *protogen.GeneratedFile, service *protogen.Service) { + serviceName := service.GoName + gfile.P("var (") + gfile.P(serviceName, "Name", "=", `"`, serviceName, `"`) + gfile.P(")") +} + +func (g *Generator) generateServiceEndpoints(gfile *protogen.GeneratedFile, service *protogen.Service) { + serviceName := service.GoName + + gfile.P("var (") + gfile.P(serviceName, "ServerEndpoints = []", microServerHttpPackage.Ident("EndpointMetadata"), "{") + + for _, method := range service.Methods { + if proto.HasExtension(method.Desc.Options(), api_options.E_Http) { + if endpoints, streaming := generateEndpoints(method); endpoints != nil { + for _, ep := range endpoints { + epath, emethod, ebody := getEndpoint(ep) + gfile.P("{") + gfile.P(`Name: "`, serviceName+"."+method.GoName, `",`) + gfile.P(`Path: "`, epath, `",`) + gfile.P(`Method: "`, emethod, `",`) + gfile.P(`Body: "`, ebody, `",`) + gfile.P(`Stream: `, streaming, `,`) + gfile.P("},") + } + } + } + } + + gfile.P("}") + gfile.P(")") +} diff --git a/variables.go b/variables.go index 05d4a01..6fc5e31 100644 --- a/variables.go +++ b/variables.go @@ -11,14 +11,15 @@ var ( gorillaMuxPackage = protogen.GoImportPath("github.com/gorilla/mux") chiPackage = protogen.GoImportPath("github.com/go-chi/chi/v5") chiMiddlewarePackage = protogen.GoImportPath("github.com/go-chi/chi/v5/middleware") - microApiPackage = protogen.GoImportPath("go.unistack.org/micro/v3/api") + microMetadataPackage = protogen.GoImportPath("go.unistack.org/micro/v3/metadata") microClientPackage = protogen.GoImportPath("go.unistack.org/micro/v3/client") microServerPackage = protogen.GoImportPath("go.unistack.org/micro/v3/server") microClientHttpPackage = protogen.GoImportPath("go.unistack.org/micro-client-http/v3") microServerHttpPackage = protogen.GoImportPath("go.unistack.org/micro-server-http/v3") - microCodecPackage = protogen.GoImportPath("go.unistack.org/micro-proto/v3/codec") - microErrorsPackage = protogen.GoImportPath("go.unistack.org/micro-proto/v3/errors") + microCodecPackage = protogen.GoImportPath("go.unistack.org/micro/v3/codec") + microErrorsPackage = protogen.GoImportPath("go.unistack.org/micro/v3/errors") + grpcPackage = protogen.GoImportPath("google.golang.org/grpc") timePackage = protogen.GoImportPath("time") deprecationComment = "// Deprecated: Do not use." - versionComment = "v3.5.3" + versionComment = "v3.10.2" )