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 c179930..c3e1b20 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.3.0 google.golang.org/protobuf v1.28.1 ) diff --git a/go.sum b/go.sum index 7acbb35..2cbff43 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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/main.go b/main.go index 9b18889..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,6 +46,7 @@ type Generator struct { fieldaligment bool tagPath string openapiFile string + reflection bool plugin *protogen.Plugin } @@ -58,6 +60,7 @@ func (g *Generator) Generate(plugin *protogen.Plugin) error { g.fieldaligment = *flagFieldaligment g.tagPath = *flagTagPath g.openapiFile = *flagOpenapiFile + g.reflection = *flagReflection plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) var genClient bool diff --git a/micro.go b/micro.go index c99d60c..dd82fb3 100644 --- a/micro.go +++ b/micro.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + "google.golang.org/protobuf/compiler/protogen" ) @@ -22,8 +24,10 @@ 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() @@ -50,3 +54,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/rpc.go b/rpc.go index ab0bedb..ee36775 100644 --- a/rpc.go +++ b/rpc.go @@ -45,6 +45,9 @@ func (g *Generator) rpcGenerate(component string, plugin *protogen.Plugin, genCl g.generateServiceServerMethods(gfile, service) g.generateServiceRegister(gfile, service) } + if component == "grpc" && g.reflection { + g.generateServiceDesc(gfile, file, service) + } } } diff --git a/util.go b/util.go index be1a34b..d913de7 100644 --- a/util.go +++ b/util.go @@ -4,7 +4,9 @@ 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" @@ -213,8 +215,14 @@ func (g *Generator) generateServiceClientMethods(gfile *protogen.GeneratedFile, } 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 := uint64(", td.Nanoseconds(), ")") + gfile.P("opts = append(opts, ", microClientPackage.Ident("WithRequestTimeout"), "(", timePackage.Ident("Nanosecond"), "* ", "td", "))") + } } } @@ -287,6 +295,11 @@ func (g *Generator) generateServiceClientMethods(gfile *protogen.GeneratedFile, 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("}") @@ -319,10 +332,16 @@ func (g *Generator) generateServiceServerMethods(gfile *protogen.GeneratedFile, 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() { @@ -598,6 +617,7 @@ func (g *Generator) generateServiceClientStreamInterface(gfile *protogen.Generat } 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() { @@ -798,3 +818,45 @@ func getGoIdentByMessage(messages []*protogen.Message, msg string) (protogen.GoI } 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() +} diff --git a/variables.go b/variables.go index 0cf425b..64fa863 100644 --- a/variables.go +++ b/variables.go @@ -12,12 +12,14 @@ var ( 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/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"