make MakeTemplate more robust (#60)
This commit is contained in:
parent
7cabe7a9d0
commit
1e8e50f4f8
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/fullstorydev/grpcurl"
|
"github.com/fullstorydev/grpcurl"
|
||||||
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc"
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
|
||||||
"github.com/jhump/protoreflect/grpcreflect"
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
@ -459,7 +458,7 @@ func main() {
|
||||||
if dsc, ok := dsc.(*desc.MessageDescriptor); ok && *msgTemplate {
|
if dsc, ok := dsc.(*desc.MessageDescriptor); ok && *msgTemplate {
|
||||||
// for messages, also show a template in JSON, to make it easier to
|
// for messages, also show a template in JSON, to make it easier to
|
||||||
// create a request to invoke an RPC
|
// 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)
|
_, formatter, err := grpcurl.RequestParserAndFormatterFor(grpcurl.Format(*format), descSource, true, false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail(err, "Failed to construct formatter for %q", *format)
|
fail(err, "Failed to construct formatter for %q", *format)
|
||||||
|
|
|
||||||
83
grpcurl.go
83
grpcurl.go
|
|
@ -21,6 +21,9 @@ import (
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
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"
|
||||||
"github.com/jhump/protoreflect/desc/protoprint"
|
"github.com/jhump/protoreflect/desc/protoprint"
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
"github.com/jhump/protoreflect/dynamic"
|
||||||
|
|
@ -352,35 +355,75 @@ func fullyConvertToDynamic(msgFact *dynamic.MessageFactory, msg proto.Message) (
|
||||||
return dm, nil
|
return dm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeTemplate fleshes out the given message so that it is a suitable template
|
// MakeTemplate returns a message instance for the given descriptor that is a
|
||||||
// for creating an instance of that message in JSON. In particular, it ensures
|
// suitable template for creating an instance of that message in JSON. In
|
||||||
// that any repeated fields (which include map fields) are not empty, so they
|
// particular, it ensures that any repeated fields (which include map fields)
|
||||||
// will render with a single element (to show the types and optionally nested
|
// are not empty, so they will render with a single element (to show the types
|
||||||
// fields). It also ensures that nested messages are not nil by setting them to
|
// and optionally nested fields). It also ensures that nested messages are not
|
||||||
// a message that is also fleshed out as a template message.
|
// nil by setting them to a message that is also fleshed out as a template
|
||||||
func MakeTemplate(msg proto.Message) proto.Message {
|
// message.
|
||||||
return makeTemplate(msg, nil)
|
func MakeTemplate(md *desc.MessageDescriptor) proto.Message {
|
||||||
|
return makeTemplate(md, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTemplate(msg proto.Message, path []*desc.MessageDescriptor) proto.Message {
|
func makeTemplate(md *desc.MessageDescriptor, path []*desc.MessageDescriptor) proto.Message {
|
||||||
dm, ok := msg.(*dynamic.Message)
|
switch md.GetFullyQualifiedName() {
|
||||||
if !ok {
|
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
|
return msg
|
||||||
}
|
case "google.protobuf.Value":
|
||||||
|
// unset kind is not allowed by JSON representation
|
||||||
// if a message is recursive structure, we don't want to blow the stack
|
// so we must give it something
|
||||||
for _, md := range path {
|
return &structpb.Value{
|
||||||
if md == dm.GetMessageDescriptor() {
|
Kind: &structpb.Value_StructValue{StructValue: &structpb.Struct{
|
||||||
// already visited this type; avoid infinite recursion
|
Fields: map[string]*structpb.Value{
|
||||||
return msg
|
"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",
|
||||||
|
}},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 dm
|
||||||
|
}
|
||||||
|
}
|
||||||
path = append(path, dm.GetMessageDescriptor())
|
path = append(path, dm.GetMessageDescriptor())
|
||||||
|
|
||||||
// for repeated fields, add a single element with default value
|
// for repeated fields, add a single element with default value
|
||||||
// and for message fields, add a message with all default fields
|
// and for message fields, add a message with all default fields
|
||||||
// that also has non-nil message and non-empty repeated fields
|
// that also has non-nil message and non-empty repeated fields
|
||||||
|
|
||||||
for _, fd := range dm.GetMessageDescriptor().GetFields() {
|
for _, fd := range dm.GetMessageDescriptor().GetFields() {
|
||||||
if fd.IsRepeated() {
|
if fd.IsRepeated() {
|
||||||
switch fd.GetType() {
|
switch fd.GetType() {
|
||||||
|
|
@ -420,10 +463,10 @@ func makeTemplate(msg proto.Message, path []*desc.MessageDescriptor) proto.Messa
|
||||||
|
|
||||||
case descpb.FieldDescriptorProto_TYPE_MESSAGE,
|
case descpb.FieldDescriptorProto_TYPE_MESSAGE,
|
||||||
descpb.FieldDescriptorProto_TYPE_GROUP:
|
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 {
|
} else if fd.GetMessageType() != nil {
|
||||||
dm.SetField(fd, makeTemplate(dynamic.NewMessage(fd.GetMessageType()), path))
|
dm.SetField(fd, makeTemplate(fd.GetMessageType(), path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dm
|
return dm
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package grpcurl_test
|
package grpcurl_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -11,6 +12,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/jsonpb"
|
"github.com/golang/protobuf/jsonpb"
|
||||||
|
jsonpbtest "github.com/golang/protobuf/jsonpb/jsonpb_test_proto"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc"
|
||||||
"github.com/jhump/protoreflect/grpcreflect"
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
|
|
@ -306,6 +308,51 @@ func fileNames(files []*desc.FileDescriptor) []string {
|
||||||
return names
|
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) {
|
func TestDescribe(t *testing.T) {
|
||||||
for _, ds := range descSources {
|
for _, ds := range descSources {
|
||||||
t.Run(ds.name, func(t *testing.T) {
|
t.Run(ds.name, func(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue