diff --git a/cmd/grpcurl/grpcurl.go b/cmd/grpcurl/grpcurl.go index 28603ef..be24759 100644 --- a/cmd/grpcurl/grpcurl.go +++ b/cmd/grpcurl/grpcurl.go @@ -12,6 +12,8 @@ import ( "strings" "time" + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" "github.com/jhump/protoreflect/desc" "github.com/jhump/protoreflect/grpcreflect" "golang.org/x/net/context" @@ -64,6 +66,8 @@ var ( `The maximum total time the operation can take. This is useful for preventing batch jobs that use grpcurl from hanging due to slow or bad network links or due to incorrect stream method usage.`) + emitDefaults = flag.Bool("emit-defaults", false, + `Emit default values from JSON-encoded responses.`) verbose = flag.Bool("v", false, `Enable verbose output.`) ) @@ -442,12 +446,17 @@ func (*handler) OnReceiveHeaders(md metadata.MD) { } } -func (h *handler) OnReceiveResponse(rsp json.RawMessage) { +func (h *handler) OnReceiveResponse(resp proto.Message) { h.respCount++ if *verbose { fmt.Print("\nResponse contents:\n") } - fmt.Println(string(rsp)) + jsm := jsonpb.Marshaler{EmitDefaults: *emitDefaults, Indent: " "} + respStr, err := jsm.MarshalToString(resp) + if err != nil { + fail(err, "failed to generate JSON form of response message") + } + fmt.Println(respStr) } func (h *handler) OnReceiveTrailers(stat *status.Status, md metadata.MD) { diff --git a/grpcurl.go b/grpcurl.go index acb0fc2..018f72b 100644 --- a/grpcurl.go +++ b/grpcurl.go @@ -263,7 +263,7 @@ type InvocationEventHandler interface { // OnReceiveHeaders is called when response headers have been received. OnReceiveHeaders(metadata.MD) // OnReceiveResponse is called for each response message received. - OnReceiveResponse(json.RawMessage) + OnReceiveResponse(proto.Message) // OnReceiveTrailers is called when response trailers and final RPC status have been received. OnReceiveTrailers(*status.Status, metadata.MD) } @@ -384,14 +384,8 @@ func invokeUnary(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDesc handler.OnReceiveHeaders(respHeaders) - var respStr string if stat.Code() == codes.OK { - jsm := jsonpb.Marshaler{EmitDefaults: true, Indent: " "} - respStr, err = jsm.MarshalToString(resp) - if err != nil { - return fmt.Errorf("failed to generate JSON form of response message: %v", err) - } - handler.OnReceiveResponse(json.RawMessage(respStr)) + handler.OnReceiveResponse(resp) } handler.OnReceiveTrailers(stat, respTrailers) @@ -447,14 +441,8 @@ func invokeClientStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.Met handler.OnReceiveHeaders(respHeaders) } - var respStr string if stat.Code() == codes.OK { - jsm := jsonpb.Marshaler{EmitDefaults: true, Indent: " "} - respStr, err = jsm.MarshalToString(resp) - if err != nil { - return fmt.Errorf("failed to generate JSON form of response message: %v", err) - } - handler.OnReceiveResponse(json.RawMessage(respStr)) + handler.OnReceiveResponse(resp) } handler.OnReceiveTrailers(stat, str.Trailer()) @@ -502,12 +490,7 @@ func invokeServerStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.Met } break } - jsm := jsonpb.Marshaler{EmitDefaults: true, Indent: " "} - respStr, err := jsm.MarshalToString(resp) - if err != nil { - return fmt.Errorf("failed to generate JSON form of response message: %v", err) - } - handler.OnReceiveResponse(json.RawMessage(respStr)) + handler.OnReceiveResponse(resp) } stat, ok := status.FromError(err) @@ -588,13 +571,7 @@ func invokeBidi(ctx context.Context, cancel context.CancelFunc, stub grpcdynamic } break } - jsm := jsonpb.Marshaler{EmitDefaults: true, Indent: " "} - respStr, err := jsm.MarshalToString(resp) - if err != nil { - return fmt.Errorf("failed to generate JSON form of response message: %v", err) - } - - handler.OnReceiveResponse(json.RawMessage(respStr)) + handler.OnReceiveResponse(resp) } if se, ok := sendErr.Load().(error); ok && se != io.EOF { diff --git a/grpcurl_test.go b/grpcurl_test.go index 8406d99..4e373f8 100644 --- a/grpcurl_test.go +++ b/grpcurl_test.go @@ -260,20 +260,6 @@ const ( "payload": { "body": "SXQncyBCdXNpbmVzcyBUaW1l" } -}` - fullResponse1 = `{ - "payload": { - "type": "COMPRESSABLE", - "body": "SXQncyBCdXNpbmVzcyBUaW1l" - }, - "username": "", - "oauthScope": "" -}` - response1 = `{ - "payload": { - "type": "COMPRESSABLE", - "body": "SXQncyBCdXNpbmVzcyBUaW1l" - } }` payload2 = `{ "payload": { @@ -306,7 +292,7 @@ func doTestUnary(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { } if h.check(t, "grpc.testing.TestService.UnaryCall", codes.OK, 1, 1) { - if h.respMessages[0] != fullResponse1 { + if h.respMessages[0] != payload1 { t.Errorf("unexpected response from RPC: expecting %s; got %s", payload1, h.respMessages[0]) } } @@ -438,7 +424,6 @@ func TestHalfDuplexStreamReflect(t *testing.T) { func doTestHalfDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { reqs := []string{payload1, payload2, payload3} - resps := []string{response1, payload2, payload3} // Success h := &handler{reqMessages: reqs} @@ -449,8 +434,8 @@ func doTestHalfDuplexStream(t *testing.T, cc *grpc.ClientConn, source Descriptor if h.check(t, "grpc.testing.TestService.HalfDuplexCall", codes.OK, 3, 3) { for i, resp := range h.respMessages { - if resp != resps[i] { - t.Errorf("unexpected response %d from RPC:\nexpecting %q\ngot %q", i, resps[i], resp) + if resp != reqs[i] { + t.Errorf("unexpected response %d from RPC:\nexpecting %q\ngot %q", i, reqs[i], resp) } } } @@ -591,8 +576,13 @@ func (h *handler) OnReceiveHeaders(md metadata.MD) { h.respHeaders = md } -func (h *handler) OnReceiveResponse(msg json.RawMessage) { - h.respMessages = append(h.respMessages, string(msg)) +func (h *handler) OnReceiveResponse(msg proto.Message) { + jsm := jsonpb.Marshaler{Indent: " "} + respStr, err := jsm.MarshalToString(msg) + if err != nil { + panic(fmt.Errorf("failed to generate JSON form of response message: %v", err)) + } + h.respMessages = append(h.respMessages, respStr) } func (h *handler) OnReceiveTrailers(stat *status.Status, md metadata.MD) {