Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

specify custom resolver #546

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
"type": "type"
}
]
},
{
"@type": "type.googleapis.com/api.FailureDetail",
"code": "001"
},
{
"@type": "type.googleapis.com/api.IndependentFailureDetail",
"code": "BAR"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ message: "internal error"
details:
{"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"description": "description", "field": "field"}]}
{"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"description": "description", "subject": "subject", "type": "type"}]}
{"@type": "type.googleapis.com/api.FailureDetail", "code": "001"}
{"@type": "type.googleapis.com/api.IndependentFailureDetail", "code": "BAR"}

Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ message: "internal error"
details:
{"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"description": "description", "field": "field"}]}
{"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"description": "description", "subject": "subject", "type": "type"}]}
{"@type": "type.googleapis.com/api.FailureDetail", "code": "001"}
{"@type": "type.googleapis.com/api.IndependentFailureDetail", "code": "BAR"}

Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ message: "internal error"
details:
{"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"description": "description", "field": "field"}]}
{"@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [{"description": "description", "subject": "subject", "type": "type"}]}
{"@type": "type.googleapis.com/api.FailureDetail", "code": "001"}
{"@type": "type.googleapis.com/api.IndependentFailureDetail", "code": "BAR"}


55 changes: 31 additions & 24 deletions format/curl/curl.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,25 @@ import (

"github.com/golang/protobuf/jsonpb" //nolint:staticcheck
"github.com/golang/protobuf/proto" //nolint:staticcheck
"github.com/jhump/protoreflect/dynamic"
"github.com/ktr0731/evans/format"
"github.com/ktr0731/evans/present"
"github.com/ktr0731/evans/present/json"
"github.com/pkg/errors"
pb "github.com/ktr0731/evans/proto"
_ "google.golang.org/genproto/googleapis/rpc/errdetails" // For calling RegisterType.
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/encoding/protojson"
protov2 "google.golang.org/protobuf/proto"
)

type responseFormatter struct {
w io.Writer

json present.Presenter
pbMarshaler *jsonpb.Marshaler
json present.Presenter
pbMarshaler *jsonpb.Marshaler
pbMarshalerV2 *protojson.MarshalOptions

wroteHeader, wroteMessage, wroteTrailer bool
}
Expand All @@ -38,6 +41,10 @@ func NewResponseFormatter(w io.Writer, emitDefaults bool) format.ResponseFormatt
pbMarshaler: &jsonpb.Marshaler{
EmitDefaults: emitDefaults,
},
pbMarshalerV2: &protojson.MarshalOptions{
EmitUnpopulated: emitDefaults,
Resolver: pb.NewAnyResolver(format.DescriptorSource),
},
}
}

Expand Down Expand Up @@ -108,13 +115,8 @@ func (p *responseFormatter) FormatStatus(status *status.Status) error {
fmt.Fprintf(p.w, "code: %s\nnumber: %d\nmessage: %q\n", status.Code().String(), status.Code(), status.Message())
if len(status.Details()) > 0 {
details := make([]string, 0, len(status.Details()))
for _, d := range status.Details() {
d, ok := d.(proto.Message)
if !ok {
continue
}
// Convert to Any to insert @type field.
m, err := p.convertProtoMessageAsAnyToMap(d)
for _, d := range status.Proto().Details {
m, err := p.convertProtoMessageToMap(d)
if err != nil {
return err
}
Expand All @@ -138,22 +140,27 @@ func (p *responseFormatter) Done() error {
}

func (p *responseFormatter) convertProtoMessageToMap(m proto.Message) (map[string]interface{}, error) {
var buf bytes.Buffer
err := p.pbMarshaler.Marshal(&buf, m)
if err != nil {
return nil, err
var b []byte
switch m.(type) {
case *dynamic.Message:
// Use jsonpb because protojson can't marshal *dynamic.Message correctly.
var buf bytes.Buffer
if err := p.pbMarshaler.Marshal(&buf, m); err != nil {
return nil, err
}

b = buf.Bytes()
case protov2.Message:
mb, err := p.pbMarshalerV2.Marshal(proto.MessageV2(m))
if err != nil {
return nil, err
}

b = mb
}
var res map[string]interface{}
if err := gojson.Unmarshal(buf.Bytes(), &res); err != nil {
if err := gojson.Unmarshal(b, &res); err != nil {
return nil, err
}
return res, nil
}

func (p *responseFormatter) convertProtoMessageAsAnyToMap(m proto.Message) (map[string]interface{}, error) {
any, err := anypb.New(proto.MessageV2(m))
if err != nil {
return nil, errors.Wrap(err, "failed to convert a message to *any.Any")
}
return p.convertProtoMessageToMap(any)
}
5 changes: 5 additions & 0 deletions format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
package format

import (
pb "github.com/ktr0731/evans/proto"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)

// DescriptorSource will be injected at runtime.
// TODO: Modify to avoid using global variable.
var DescriptorSource pb.DescriptorSource

// ResponseFormatter provides formatting feature for gRPC response.
type ResponseFormatter struct {
enrich bool
Expand Down
65 changes: 38 additions & 27 deletions format/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import (

"github.com/golang/protobuf/jsonpb" //nolint:staticcheck
"github.com/golang/protobuf/proto" //nolint:staticcheck
"github.com/jhump/protoreflect/dynamic"
"github.com/ktr0731/evans/format"
"github.com/ktr0731/evans/present"
"github.com/ktr0731/evans/present/json"
"github.com/pkg/errors"
pb "github.com/ktr0731/evans/proto"
_ "google.golang.org/genproto/googleapis/rpc/errdetails" // For calling RegisterType.
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/encoding/protojson"
protov2 "google.golang.org/protobuf/proto"
)

// responseFormatter is a formatter that formats *usecase.GRPCResponse into a JSON object.
Expand All @@ -32,14 +34,23 @@ type responseFormatter struct {
Messages []map[string]interface{} `json:"messages,omitempty"`
Trailer *metadata.MD `json:"trailer,omitempty"`
}
p present.Presenter
pbMarshaler *jsonpb.Marshaler
p present.Presenter
pbMarshaler *jsonpb.Marshaler
pbMarshalerV2 *protojson.MarshalOptions
}

func NewResponseFormatter(w io.Writer, emitDefaults bool) format.ResponseFormatterInterface {
return &responseFormatter{w: w, p: json.NewPresenter(" "), pbMarshaler: &jsonpb.Marshaler{
EmitDefaults: emitDefaults,
}}
return &responseFormatter{
w: w,
p: json.NewPresenter(" "),
pbMarshaler: &jsonpb.Marshaler{
EmitDefaults: emitDefaults,
},
pbMarshalerV2: &protojson.MarshalOptions{
EmitUnpopulated: emitDefaults,
Resolver: pb.NewAnyResolver(format.DescriptorSource),
},
}
}

func (p *responseFormatter) FormatHeader(header metadata.MD) {
Expand All @@ -63,13 +74,8 @@ func (p *responseFormatter) FormatStatus(s *status.Status) error {
var details []interface{}
if len(s.Details()) != 0 {
details = make([]interface{}, 0, len(s.Details()))
for _, d := range s.Details() {
d, ok := d.(proto.Message)
if !ok {
continue
}
// Convert to Any to insert @type field.
m, err := p.convertProtoMessageAsAnyToMap(d)
for _, d := range s.Proto().Details {
m, err := p.convertProtoMessageToMap(d)
if err != nil {
return err
}
Expand Down Expand Up @@ -101,22 +107,27 @@ func (p *responseFormatter) Done() error {
}

func (p *responseFormatter) convertProtoMessageToMap(m proto.Message) (map[string]interface{}, error) {
var buf bytes.Buffer
err := p.pbMarshaler.Marshal(&buf, m)
if err != nil {
return nil, err
var b []byte
switch m.(type) {
case *dynamic.Message:
// Use jsonpb because protojson can't marshal *dynamic.Message correctly.
var buf bytes.Buffer
if err := p.pbMarshaler.Marshal(&buf, m); err != nil {
return nil, err
}

b = buf.Bytes()
case protov2.Message:
mb, err := p.pbMarshalerV2.Marshal(proto.MessageV2(m))
if err != nil {
return nil, err
}

b = mb
}
var res map[string]interface{}
if err := gojson.Unmarshal(buf.Bytes(), &res); err != nil {
if err := gojson.Unmarshal(b, &res); err != nil {
return nil, err
}
return res, nil
}

func (p *responseFormatter) convertProtoMessageAsAnyToMap(m proto.Message) (map[string]interface{}, error) {
any, err := anypb.New(proto.MessageV2(m))
if err != nil {
return nil, errors.Wrap(err, "failed to convert a message to *any.Any")
}
return p.convertProtoMessageToMap(any)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/ktr0731/go-prompt v0.2.4
github.com/ktr0731/go-shellstring v0.1.3
github.com/ktr0731/go-updater v0.1.5
github.com/ktr0731/grpc-test v0.1.10
github.com/ktr0731/grpc-test v0.1.12
github.com/ktr0731/grpc-web-go-client v0.2.8
github.com/manifoldco/promptui v0.9.0
github.com/matryer/moq v0.2.7
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -731,8 +731,8 @@ github.com/ktr0731/go-shellstring v0.1.3/go.mod h1:f0/XX0hsm6A02Es/UDQ9MGd3VqpCH
github.com/ktr0731/go-updater v0.1.5 h1:AiaQxJoI0OTGqvsJYdIITCOaEzQQOqU2MHKUwttVShY=
github.com/ktr0731/go-updater v0.1.5/go.mod h1:dsdOg7a9sj6ttcOU5ZxPCtKdm9WeB1hwcX/7oKgz9/0=
github.com/ktr0731/grpc-test v0.1.4/go.mod h1:v47616grayBYXQveGWxO3OwjLB3nEEnHsZuMTc73FM0=
github.com/ktr0731/grpc-test v0.1.10 h1:W+fdfNrcSY0+9aiIIx+CWpFcNjdcqysPQchDcuMk4Pk=
github.com/ktr0731/grpc-test v0.1.10/go.mod h1:AP4+ZrqSzdDaUNhAsp2fye06MXO2fdYY6YQJifb588M=
github.com/ktr0731/grpc-test v0.1.12 h1:Yha+zH2hB48huOfbsEMfyG7FeHCrVWq4fYmHfr3iH3U=
github.com/ktr0731/grpc-test v0.1.12/go.mod h1:AP4+ZrqSzdDaUNhAsp2fye06MXO2fdYY6YQJifb588M=
github.com/ktr0731/grpc-web-go-client v0.2.8 h1:nUf9p+YWirmFwmH0mwtAWhuXvzovc+/3C/eAY2Fshnk=
github.com/ktr0731/grpc-web-go-client v0.2.8/go.mod h1:1Iac8gFJvC/DRfZoGnFZsfEbEq/wQFK+2Ve1o3pHkCQ=
github.com/ktr0731/modfile v1.11.2/go.mod h1:LzNwnHJWHbuDh3BO17lIqzqDldXqGu1HCydWH3SinE0=
Expand Down
29 changes: 29 additions & 0 deletions grpc/grpcreflection/reflection.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@ package grpcreflection

import (
"context"
"fmt"
"strings"

"github.com/jhump/protoreflect/desc"
gr "github.com/jhump/protoreflect/grpcreflect"
"github.com/ktr0731/evans/proto"
"github.com/ktr0731/grpc-web-go-client/grpcweb"
"github.com/ktr0731/grpc-web-go-client/grpcweb/grpcweb_reflection_v1alpha"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
)

// ServiceName represents the gRPC reflection service name.
Expand All @@ -28,6 +33,7 @@ type Client interface {
// ListPackages returns these errors:
// - ErrTLSHandshakeFailed: TLS misconfig.
ListPackages() ([]*desc.FileDescriptor, error)
FindSymbol(name string) (protoreflect.MessageDescriptor, error)
// Reset clears internal states of Client.
Reset()
}
Expand Down Expand Up @@ -90,6 +96,29 @@ func (c *client) ListPackages() ([]*desc.FileDescriptor, error) {
return fds, nil
}

func (c *client) FindSymbol(name string) (protoreflect.MessageDescriptor, error) {
prfd, err := c.client.FileContainingSymbol(name)
if err != nil {
return nil, errors.Wrapf(err, "failed to find file containing symbol %s", name)
}

if err := proto.RegisterFileAndType(prfd); err != nil {
return nil, errors.Wrap(err, "failed to register file dscriptor")
}

fd, err := protodesc.NewFile(prfd.AsFileDescriptorProto(), protoregistry.GlobalFiles)
if err != nil {
return nil, errors.Wrapf(err, "failed to find file containing symbol %s", name)
}

md := fd.Messages().ByName(protoreflect.FullName(name).Name())
if md == nil {
return nil, fmt.Errorf("failed to find message '%s'", name)
}

return md, nil
}

func (c *client) Reset() {
c.client.Reset()
}
37 changes: 2 additions & 35 deletions idl/proto/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ import (
"github.com/ktr0731/evans/grpc"
"github.com/ktr0731/evans/grpc/grpcreflection"
"github.com/ktr0731/evans/idl"
pb "github.com/ktr0731/evans/proto"
"github.com/pkg/errors"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/dynamicpb"
)

type spec struct {
Expand Down Expand Up @@ -160,7 +158,7 @@ func newSpec(fds []*desc.FileDescriptor) (idl.Spec, error) {
msgDescs = make(map[string]*desc.MessageDescriptor)
)
for _, f := range fds {
if err := registerFileAndType(f); err != nil {
if err := pb.RegisterFileAndType(f); err != nil {
return nil, errors.Wrap(err, "failed to register file descriptor")
}

Expand Down Expand Up @@ -195,37 +193,6 @@ func newSpec(fds []*desc.FileDescriptor) (idl.Spec, error) {
}, nil
}

func registerFileAndType(fd *desc.FileDescriptor) error {
deps := fd.GetDependencies()
for _, d := range deps {
if err := registerFileAndType(d); err != nil {
return err
}
}

prfd, err := protodesc.NewFile(fd.AsFileDescriptorProto(), protoregistry.GlobalFiles)
if err != nil {
return errors.Wrap(err, "failed to new file descriptor")
}

if _, err := protoregistry.GlobalFiles.FindFileByPath(prfd.Path()); errors.Is(err, protoregistry.NotFound) {
if err := protoregistry.GlobalFiles.RegisterFile(prfd); err != nil {
return err
}
}

for i := 0; i < prfd.Messages().Len(); i++ {
md := prfd.Messages().Get(i)
if _, err := protoregistry.GlobalTypes.FindMessageByName(md.FullName()); errors.Is(err, protoregistry.NotFound) {
if err := protoregistry.GlobalTypes.RegisterMessage(dynamicpb.NewMessageType(md)); err != nil {
return err
}
}
}

return nil
}

// FullyQualifiedServiceName returns the fully-qualified service name.
func FullyQualifiedServiceName(pkg, svc string) string {
var s []string
Expand Down
Loading