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

Print count of unrecognized bytes in "buf curl" #2586

Merged
merged 1 commit into from
Nov 14, 2023
Merged
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
40 changes: 38 additions & 2 deletions private/buf/bufcurl/invoker.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ func (inv *invoker) handleResponse(data []byte, msg *dynamicpb.Message) error {
protoencoding.JSONMarshalerWithEmitUnpopulated(),
)
}
unrecognized := countUnrecognized(msg.ProtoReflect())
if unrecognized > 0 {
inv.printer.Printf("Response message (%s) contained %d bytes of unrecognized fields.",
msg.ProtoReflect().Descriptor().FullName(), unrecognized)
}
outputBytes, err := protoencoding.NewJSONMarshaler(inv.res, jsonMarshalerOptions...).Marshal(msg)
if err != nil {
return err
Expand Down Expand Up @@ -437,6 +442,37 @@ func (s *streamMessageProvider) next(msg proto.Message) error {
}
return fmt.Errorf("%s at offset %d: %w", s.name, s.dec.InputOffset(), err)
}
proto.Reset(msg)
return protoencoding.NewJSONUnmarshaler(s.res).Unmarshal(jsonData, msg)
return protoencoding.NewJSONUnmarshaler(
s.res, protoencoding.JSONUnmarshalerWithDisallowUnknown(),
).Unmarshal(jsonData, msg)
}

func countUnrecognized(msg protoreflect.Message) int {
var count int
msg.Range(func(field protoreflect.FieldDescriptor, val protoreflect.Value) bool {
switch {
case field.IsMap() && isMessageKind(field.MapValue().Kind()):
// Note: Technically, each message entry could have had unrecognized field
// bytes, but they are discarded by the runtime. So we can only look at
// unrecognized fields in message values inside the map.
mapVal := val.Map()
mapVal.Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool {
count += countUnrecognized(v.Message())
return true
})
case field.IsList() && isMessageKind(field.Kind()):
listVal := val.List()
for i, length := 0, listVal.Len(); i < length; i++ {
count += countUnrecognized(listVal.Get(i).Message())
}
case isMessageKind(field.Kind()):
count += countUnrecognized(val.Message())
}
return true
})
return count + len(msg.GetUnknown())
}

func isMessageKind(k protoreflect.Kind) bool {
return k == protoreflect.MessageKind || k == protoreflect.GroupKind
}
16 changes: 10 additions & 6 deletions private/pkg/protoencoding/json_unmarshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,24 @@ import (
)

type jsonUnmarshaler struct {
resolver Resolver
resolver Resolver
disallowUnknown bool
}

func newJSONUnmarshaler(resolver Resolver) Unmarshaler {
return &jsonUnmarshaler{
func newJSONUnmarshaler(resolver Resolver, options ...JSONUnmarshalerOption) Unmarshaler {
jsonUnmarshaler := &jsonUnmarshaler{
resolver: resolver,
}
for _, option := range options {
option(jsonUnmarshaler)
}
return jsonUnmarshaler
}

func (m *jsonUnmarshaler) Unmarshal(data []byte, message proto.Message) error {
options := protojson.UnmarshalOptions{
Resolver: m.resolver,
// TODO: make this an option
DiscardUnknown: true,
Resolver: m.resolver,
DiscardUnknown: !m.disallowUnknown,
}
return options.Unmarshal(data, message)
}
14 changes: 12 additions & 2 deletions private/pkg/protoencoding/protoencoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,18 @@ func NewWireUnmarshaler(resolver Resolver) Unmarshaler {
// NewJSONUnmarshaler returns a new Unmarshaler for json.
//
// resolver can be nil if unknown and are only needed for extensions.
func NewJSONUnmarshaler(resolver Resolver) Unmarshaler {
return newJSONUnmarshaler(resolver)
func NewJSONUnmarshaler(resolver Resolver, options ...JSONUnmarshalerOption) Unmarshaler {
return newJSONUnmarshaler(resolver, options...)
}

// JSONUnmarshalerOption is an option for a new JSONUnmarshaler.
type JSONUnmarshalerOption func(*jsonUnmarshaler)

// JSONUnmarshalerWithDisallowUnknown says to disallow unrecognized fields.
func JSONUnmarshalerWithDisallowUnknown() JSONUnmarshalerOption {
return func(jsonUnmarshaler *jsonUnmarshaler) {
jsonUnmarshaler.disallowUnknown = true
}
}

// NewTxtpbUnmarshaler returns a new Unmarshaler for txtpb.
Expand Down