From 03c21551f37ce733656176f1936d7c35ebe3f26e Mon Sep 17 00:00:00 2001 From: Imre Rad Date: Tue, 10 May 2022 14:52:59 +0200 Subject: [PATCH] new feature: use grpcurl as an offline tool to convert JSON into proto --- cmd/grpcurl/grpcurl.go | 65 +++++++++++++++++++++++++++++------------- invoke.go | 58 +++++++++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 32 deletions(-) diff --git a/cmd/grpcurl/grpcurl.go b/cmd/grpcurl/grpcurl.go index 6707837b..205424a0 100644 --- a/cmd/grpcurl/grpcurl.go +++ b/cmd/grpcurl/grpcurl.go @@ -152,6 +152,13 @@ var ( permitted if they are both set to the same value, to increase backwards compatibility with earlier releases that allowed both to be set).`)) reflection = optionalBoolFlag{val: true} + + convertMessage = flags.String("convert-message", "", prettify(` + Turns the tool into an offline converter; the proto representation of + the JSON input will be printed to the standard output. + This parameter should specify the fully qualified name of the protobuf + message definition (e.g. some.service/ReadRequest). + `)) ) func init() { @@ -301,28 +308,35 @@ func main() { } args := flags.Args() + + convertJson := *convertMessage != "" - if len(args) == 0 { - fail(nil, "Too few arguments.") - } var target string - if args[0] != "list" && args[0] != "describe" { - target = args[0] - args = args[1:] - } - - if len(args) == 0 { - fail(nil, "Too few arguments.") - } var list, describe, invoke bool - if args[0] == "list" { - list = true - args = args[1:] - } else if args[0] == "describe" { - describe = true - args = args[1:] + if len(args) != 0 { + if convertJson { + fail(nil, "Target service should be omitted for offline conversions.") + } + + if args[0] != "list" && args[0] != "describe" { + target = args[0] + args = args[1:] + } + + if args[0] == "list" { + list = true + args = args[1:] + } else if args[0] == "describe" { + describe = true + args = args[1:] + } else { + invoke = true + } + } else { - invoke = true + if !convertJson { + fail(nil, "Too few arguments.") + } } verbosityLevel := 0 @@ -341,7 +355,7 @@ func main() { symbol = args[0] args = args[1:] } else { - if *data != "" { + if *data != "" && !convertJson { warn("The -d argument is not used with 'list' or 'describe' verb.") } if len(rpcHeaders) > 0 { @@ -675,9 +689,10 @@ func main() { } else { // Invoke an RPC - if cc == nil { + if cc == nil && !convertJson { cc = dial() } + var in io.Reader if *data == "@" { in = os.Stdin @@ -698,6 +713,16 @@ func main() { if err != nil { fail(err, "Failed to construct request parser and formatter for %q", *format) } + + if convertJson { + proto, err:= grpcurl.ConvertMessage(descSource, *convertMessage, rf.Next) + if err != nil { + fail(err, "Error converting message %q", *convertMessage) + } + os.Stdout.Write(proto) + os.Exit(0) + } + h := &grpcurl.DefaultEventHandler{ Out: os.Stdout, Formatter: formatter, diff --git a/invoke.go b/invoke.go index b5bae4b1..dc6ab19c 100644 --- a/invoke.go +++ b/invoke.go @@ -119,18 +119,7 @@ func InvokeRPC(ctx context.Context, source DescriptorSource, ch grpcdynamic.Chan handler.OnResolveMethod(mtd) - // we also download any applicable extensions so we can provide full support for parsing user-provided data - var ext dynamic.ExtensionRegistry - alreadyFetched := map[string]bool{} - if err = fetchAllExtensions(source, &ext, mtd.GetInputType(), alreadyFetched); err != nil { - return fmt.Errorf("error resolving server extensions for message %s: %v", mtd.GetInputType().GetFullyQualifiedName(), err) - } - if err = fetchAllExtensions(source, &ext, mtd.GetOutputType(), alreadyFetched); err != nil { - return fmt.Errorf("error resolving server extensions for message %s: %v", mtd.GetOutputType().GetFullyQualifiedName(), err) - } - - msgFactory := dynamic.NewMessageFactoryWithExtensionRegistry(&ext) - req := msgFactory.NewMessage(mtd.GetInputType()) + req, msgFactory, err:= getExtensions(source, mtd.GetInputType()) handler.OnSendHeaders(md) ctx = metadata.NewOutgoingContext(ctx, md) @@ -403,3 +392,48 @@ func parseSymbol(svcAndMethod string) (string, string) { } return svcAndMethod[:pos], svcAndMethod[pos+1:] } + +func ConvertMessage(source DescriptorSource, messageFullName string, requestData RequestSupplier) ([]byte, error) { + dsc, err := source.FindSymbol(messageFullName) + if err != nil { + return nil, err + } + md, ok := dsc.(*desc.MessageDescriptor) + if ! ok { + return nil, fmt.Errorf("%q should point to a message descriptor instead of %d:", messageFullName, md) + } + + req, _, err:= getExtensions(source, md) + if err != nil { + return nil, err + } + + err = requestData(req) + if err != nil { + return nil, err + } + + re, err:= proto.Marshal(req) + if err != nil { + return nil, err + } + + return re, nil +} + +func getExtensions(source DescriptorSource, md *desc.MessageDescriptor) (req proto.Message, msgFactory *dynamic.MessageFactory, err error) { + // we also download any applicable extensions so we can provide full support for parsing user-provided data + var ext dynamic.ExtensionRegistry + alreadyFetched := map[string]bool{} + if err = fetchAllExtensions(source, &ext, md, alreadyFetched); err != nil { + return nil, nil, fmt.Errorf("error resolving server extensions for message %s: %v", md.GetFullyQualifiedName(), err) + } + if err = fetchAllExtensions(source, &ext, md, alreadyFetched); err != nil { + return nil, nil, fmt.Errorf("error resolving server extensions for message %s: %v", md.GetFullyQualifiedName(), err) + } + + msgFactory = dynamic.NewMessageFactoryWithExtensionRegistry(&ext) + req = msgFactory.NewMessage(md) + + return req, msgFactory, nil +}