make MakeTemplate more robust (#60)
This commit is contained in:
parent
7cabe7a9d0
commit
1e8e50f4f8
|
|
@ -15,7 +15,6 @@ import (
|
|||
"github.com/fullstorydev/grpcurl"
|
||||
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/jhump/protoreflect/dynamic"
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -459,7 +458,7 @@ func main() {
|
|||
if dsc, ok := dsc.(*desc.MessageDescriptor); ok && *msgTemplate {
|
||||
// for messages, also show a template in JSON, to make it easier to
|
||||
// create a request to invoke an RPC
|
||||
tmpl := grpcurl.MakeTemplate(dynamic.NewMessage(dsc))
|
||||
tmpl := grpcurl.MakeTemplate(dsc)
|
||||
_, formatter, err := grpcurl.RequestParserAndFormatterFor(grpcurl.Format(*format), descSource, true, false, nil)
|
||||
if err != nil {
|
||||
fail(err, "Failed to construct formatter for %q", *format)
|
||||
|
|
|
|||
79
grpcurl.go
79
grpcurl.go
|
|
@ -21,6 +21,9 @@ import (
|
|||
|
||||
"github.com/golang/protobuf/proto"
|
||||
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"github.com/golang/protobuf/ptypes/struct"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/jhump/protoreflect/desc/protoprint"
|
||||
"github.com/jhump/protoreflect/dynamic"
|
||||
|
|
@ -352,35 +355,75 @@ func fullyConvertToDynamic(msgFact *dynamic.MessageFactory, msg proto.Message) (
|
|||
return dm, nil
|
||||
}
|
||||
|
||||
// MakeTemplate fleshes out the given message so that it is a suitable template
|
||||
// for creating an instance of that message in JSON. In particular, it ensures
|
||||
// that any repeated fields (which include map fields) are not empty, so they
|
||||
// will render with a single element (to show the types and optionally nested
|
||||
// fields). It also ensures that nested messages are not nil by setting them to
|
||||
// a message that is also fleshed out as a template message.
|
||||
func MakeTemplate(msg proto.Message) proto.Message {
|
||||
return makeTemplate(msg, nil)
|
||||
// MakeTemplate returns a message instance for the given descriptor that is a
|
||||
// suitable template for creating an instance of that message in JSON. In
|
||||
// particular, it ensures that any repeated fields (which include map fields)
|
||||
// are not empty, so they will render with a single element (to show the types
|
||||
// and optionally nested fields). It also ensures that nested messages are not
|
||||
// nil by setting them to a message that is also fleshed out as a template
|
||||
// message.
|
||||
func MakeTemplate(md *desc.MessageDescriptor) proto.Message {
|
||||
return makeTemplate(md, nil)
|
||||
}
|
||||
|
||||
func makeTemplate(msg proto.Message, path []*desc.MessageDescriptor) proto.Message {
|
||||
dm, ok := msg.(*dynamic.Message)
|
||||
if !ok {
|
||||
func makeTemplate(md *desc.MessageDescriptor, path []*desc.MessageDescriptor) proto.Message {
|
||||
switch md.GetFullyQualifiedName() {
|
||||
case "google.protobuf.Any":
|
||||
// empty type URL is not allowed by JSON representation
|
||||
// so we must give it a dummy type
|
||||
msg, _ := ptypes.MarshalAny(&empty.Empty{})
|
||||
return msg
|
||||
case "google.protobuf.Value":
|
||||
// unset kind is not allowed by JSON representation
|
||||
// so we must give it something
|
||||
return &structpb.Value{
|
||||
Kind: &structpb.Value_StructValue{StructValue: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"google.protobuf.Value": {Kind: &structpb.Value_StringValue{
|
||||
StringValue: "supports arbitrary JSON",
|
||||
}},
|
||||
},
|
||||
}},
|
||||
}
|
||||
case "google.protobuf.ListValue":
|
||||
return &structpb.ListValue{
|
||||
Values: []*structpb.Value{
|
||||
{
|
||||
Kind: &structpb.Value_StructValue{StructValue: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"google.protobuf.ListValue": {Kind: &structpb.Value_StringValue{
|
||||
StringValue: "is an array of arbitrary JSON values",
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
case "google.protobuf.Struct":
|
||||
return &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"google.protobuf.Struct": {Kind: &structpb.Value_StringValue{
|
||||
StringValue: "supports arbitrary JSON objects",
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// if a message is recursive structure, we don't want to blow the stack
|
||||
for _, md := range path {
|
||||
if md == dm.GetMessageDescriptor() {
|
||||
dm := dynamic.NewMessage(md)
|
||||
|
||||
// if the message is a recursive structure, we don't want to blow the stack
|
||||
for _, seen := range path {
|
||||
if seen == md {
|
||||
// already visited this type; avoid infinite recursion
|
||||
return msg
|
||||
return dm
|
||||
}
|
||||
}
|
||||
|
||||
path = append(path, dm.GetMessageDescriptor())
|
||||
|
||||
// for repeated fields, add a single element with default value
|
||||
// and for message fields, add a message with all default fields
|
||||
// that also has non-nil message and non-empty repeated fields
|
||||
|
||||
for _, fd := range dm.GetMessageDescriptor().GetFields() {
|
||||
if fd.IsRepeated() {
|
||||
switch fd.GetType() {
|
||||
|
|
@ -420,10 +463,10 @@ func makeTemplate(msg proto.Message, path []*desc.MessageDescriptor) proto.Messa
|
|||
|
||||
case descpb.FieldDescriptorProto_TYPE_MESSAGE,
|
||||
descpb.FieldDescriptorProto_TYPE_GROUP:
|
||||
dm.AddRepeatedField(fd, makeTemplate(dynamic.NewMessage(fd.GetMessageType()), path))
|
||||
dm.AddRepeatedField(fd, makeTemplate(fd.GetMessageType(), path))
|
||||
}
|
||||
} else if fd.GetMessageType() != nil {
|
||||
dm.SetField(fd, makeTemplate(dynamic.NewMessage(fd.GetMessageType()), path))
|
||||
dm.SetField(fd, makeTemplate(fd.GetMessageType(), path))
|
||||
}
|
||||
}
|
||||
return dm
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package grpcurl_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
|
@ -11,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
jsonpbtest "github.com/golang/protobuf/jsonpb/jsonpb_test_proto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
|
|
@ -306,6 +308,51 @@ func fileNames(files []*desc.FileDescriptor) []string {
|
|||
return names
|
||||
}
|
||||
|
||||
const expectKnownType = `{
|
||||
"dur": "0s",
|
||||
"ts": "1970-01-01T00:00:00Z",
|
||||
"dbl": 0,
|
||||
"flt": 0,
|
||||
"i64": "0",
|
||||
"u64": "0",
|
||||
"i32": 0,
|
||||
"u32": 0,
|
||||
"bool": false,
|
||||
"str": "",
|
||||
"bytes": null,
|
||||
"st": {"google.protobuf.Struct": "supports arbitrary JSON objects"},
|
||||
"an": {"@type": "type.googleapis.com/google.protobuf.Empty", "value": {}},
|
||||
"lv": [{"google.protobuf.ListValue": "is an array of arbitrary JSON values"}],
|
||||
"val": {"google.protobuf.Value": "supports arbitrary JSON"}
|
||||
}`
|
||||
|
||||
func TestMakeTemplateKnownTypes(t *testing.T) {
|
||||
descriptor, err := desc.LoadMessageDescriptorForMessage((*jsonpbtest.KnownTypes)(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load descriptor: %v", err)
|
||||
}
|
||||
message := MakeTemplate(descriptor)
|
||||
|
||||
jsm := jsonpb.Marshaler{EmitDefaults: true}
|
||||
out, err := jsm.MarshalToString(message)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal to JSON: %v", err)
|
||||
}
|
||||
|
||||
// make sure template JSON matches expected
|
||||
var actual, expected interface{}
|
||||
if err := json.Unmarshal([]byte(out), &actual); err != nil {
|
||||
t.Fatalf("failed to parse actual JSON: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(expectKnownType), &expected); err != nil {
|
||||
t.Fatalf("failed to parse expected JSON: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("template message is not as expected; want:\n%s\ngot:\n%s", expectKnownType, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribe(t *testing.T) {
|
||||
for _, ds := range descSources {
|
||||
t.Run(ds.name, func(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue