Skip to content

Commit

Permalink
Add emit-defaults flag to buf curl (#2132)
Browse files Browse the repository at this point in the history
  • Loading branch information
bufdev committed May 30, 2023
1 parent 4be1836 commit 0e08a8f
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 46 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## [Unreleased]

- Add `--emit-defaults` flag to `buf curl` to emit default values in JSON-encoded responses.
- Indent JSON-encoded responses from `buf curl` by default.
- Log a warning in case an import statement does not point to a file in the module, a file in a
direct dependency, or a well-known type file.

Expand Down
39 changes: 25 additions & 14 deletions private/buf/bufcurl/invoker.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,33 @@ func (p protoCodec) Unmarshal(bytes []byte, a any) error {
type invokeClient = connect.Client[dynamicpb.Message, deferredMessage]

type invoker struct {
md protoreflect.MethodDescriptor
res protoencoding.Resolver
client *invokeClient
output io.Writer
errOutput io.Writer
printer verbose.Printer
md protoreflect.MethodDescriptor
res protoencoding.Resolver
emitDefaults bool
client *invokeClient
output io.Writer
errOutput io.Writer
printer verbose.Printer
}

// NewInvoker creates a new invoker for invoking the method described by the
// given descriptor. The given writer is used to write the output response(s)
// in JSON format. The given resolver is used to resolve Any messages and
// extensions that appear in the input or output. Other parameters are used
// to create a Connect client, for issuing the RPC.
func NewInvoker(container appflag.Container, md protoreflect.MethodDescriptor, res protoencoding.Resolver, httpClient connect.HTTPClient, opts []connect.ClientOption, url string, out io.Writer) Invoker {
func NewInvoker(container appflag.Container, md protoreflect.MethodDescriptor, res protoencoding.Resolver, emitDefaults bool, httpClient connect.HTTPClient, opts []connect.ClientOption, url string, out io.Writer) Invoker {
opts = append(opts, connect.WithCodec(protoCodec{}))
// TODO: could also provide custom compressor implementations that could give us
// optics into when request and response messages are compressed (which could be
// useful to include in verbose output).
return &invoker{
md: md,
res: res,
output: out,
printer: container.VerbosePrinter(),
errOutput: container.Stderr(),
client: connect.NewClient[dynamicpb.Message, deferredMessage](httpClient, url, opts...),
md: md,
res: res,
emitDefaults: emitDefaults,
output: out,
printer: container.VerbosePrinter(),
errOutput: container.Stderr(),
client: connect.NewClient[dynamicpb.Message, deferredMessage](httpClient, url, opts...),
}
}

Expand Down Expand Up @@ -279,7 +281,16 @@ func (inv *invoker) handleResponse(data []byte, msg *dynamicpb.Message) error {
if err := protoencoding.NewWireUnmarshaler(inv.res).Unmarshal(data, msg); err != nil {
return err
}
outputBytes, err := protoencoding.NewJSONMarshalerIndent(inv.res).Marshal(msg)
jsonMarshalerOptions := []protoencoding.JSONMarshalerOption{
protoencoding.JSONMarshalerWithIndent(),
}
if inv.emitDefaults {
jsonMarshalerOptions = append(
jsonMarshalerOptions,
protoencoding.JSONMarshalerWithEmitUnpopulated(),
)
}
outputBytes, err := protoencoding.NewJSONMarshaler(inv.res, jsonMarshalerOptions...).Marshal(msg)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion private/buf/bufprint/bufprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func WithTabWriter(

// printProtoMessageJSON prints the Protobuf message as JSON.
func printProtoMessageJSON(writer io.Writer, message proto.Message) error {
data, err := protoencoding.NewJSONMarshalerIndent(nil).Marshal(message)
data, err := protoencoding.NewJSONMarshaler(nil, protoencoding.JSONMarshalerWithIndent()).Marshal(message)
if err != nil {
return err
}
Expand Down
20 changes: 16 additions & 4 deletions private/buf/cmd/buf/command/curl/curl.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ const (
headerFlagShortName = "H"
dataFlagName = "data"
dataFlagShortName = "d"
outputFlagName = "output"
outputFlagShortName = "o"

// Output flags
outputFlagName = "output"
outputFlagShortName = "o"
emitDefaultsFlagName = "emit-defaults"
)

// NewCommand returns a new Command.
Expand Down Expand Up @@ -223,7 +226,10 @@ type flags struct {
NetrcFile string
Headers []string
Data string
Output string

// Output options
Output string
EmitDefaults bool

// so we can inquire about which flags present on command-line
// TODO: ideally we'd use cobra directly instead of having the appcmd wrapper,
Expand Down Expand Up @@ -468,6 +474,12 @@ provided via stdin as a file descriptor set or image`,
"",
`Path to output file to create with response data. If absent, response is printed to stdout`,
)
flagSet.BoolVar(
&f.EmitDefaults,
emitDefaultsFlagName,
false,
`Emit default values for JSON-encoded responses.`,
)
}

func (f *flags) validate(isSecure bool) error {
Expand Down Expand Up @@ -943,7 +955,7 @@ func run(ctx context.Context, container appflag.Container, f *flags) (err error)
}

// Now we can finally issue the RPC
invoker := bufcurl.NewInvoker(container, methodDescriptor, res, transport, clientOptions, container.Arg(0), output)
invoker := bufcurl.NewInvoker(container, methodDescriptor, res, f.EmitDefaults, transport, clientOptions, container.Arg(0), output)
return invoker.Invoke(ctx, dataSource, dataReader, requestHeaders)
}

Expand Down
2 changes: 1 addition & 1 deletion private/bufpkg/bufimage/bufimageutil/bufimageutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ func runSourceCodeInfoTest(t *testing.T, typename string, expectedFile string, o

imageFile := filteredImage.GetFile("test.proto")
sourceCodeInfo := imageFile.FileDescriptor().GetSourceCodeInfo()
actual, err := protoencoding.NewJSONMarshalerIndent(nil).Marshal(sourceCodeInfo)
actual, err := protoencoding.NewJSONMarshaler(nil, protoencoding.JSONMarshalerWithIndent()).Marshal(sourceCodeInfo)
require.NoError(t, err)

checkExpectation(t, ctx, actual, bucket, expectedFile)
Expand Down
24 changes: 14 additions & 10 deletions private/pkg/protoencoding/json_marshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,30 @@ import (
)

type jsonMarshaler struct {
resolver Resolver
indent string
useProtoNames bool
resolver Resolver
indent string
useProtoNames bool
emitUnpopulated bool
}

func newJSONMarshaler(resolver Resolver, indent string, useProtoNames bool) Marshaler {
return &jsonMarshaler{
resolver: resolver,
indent: indent,
useProtoNames: useProtoNames,
func newJSONMarshaler(resolver Resolver, options ...JSONMarshalerOption) Marshaler {
jsonMarshaler := &jsonMarshaler{
resolver: resolver,
}
for _, option := range options {
option(jsonMarshaler)
}
return jsonMarshaler
}

func (m *jsonMarshaler) Marshal(message proto.Message) ([]byte, error) {
if err := ReparseUnrecognized(m.resolver, message.ProtoReflect()); err != nil {
return nil, err
}
options := protojson.MarshalOptions{
Resolver: m.resolver,
UseProtoNames: m.useProtoNames,
Resolver: m.resolver,
UseProtoNames: m.useProtoNames,
EmitUnpopulated: m.emitUnpopulated,
}
data, err := options.Marshal(message)
if err != nil {
Expand Down
36 changes: 22 additions & 14 deletions private/pkg/protoencoding/protoencoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,32 @@ func NewWireMarshaler() Marshaler {
//
// This has the potential to be unstable over time.
// resolver can be nil if unknown and are only needed for extensions.
func NewJSONMarshaler(resolver Resolver) Marshaler {
return newJSONMarshaler(resolver, "", false)
func NewJSONMarshaler(resolver Resolver, options ...JSONMarshalerOption) Marshaler {
return newJSONMarshaler(resolver, options...)
}

// NewJSONMarshalerIndent returns a new Marshaler for JSON with indents.
//
// This has the potential to be unstable over time.
// resolver can be nil if unknown and are only needed for extensions.
func NewJSONMarshalerIndent(resolver Resolver) Marshaler {
return newJSONMarshaler(resolver, " ", false)
// JSONMarshalerOption is an option for a new JSONMarshaler.
type JSONMarshalerOption func(*jsonMarshaler)

// JSONMarshalerWithIndent says to use an indent of two spaces.
func JSONMarshalerWithIndent() JSONMarshalerOption {
return func(jsonMarshaler *jsonMarshaler) {
jsonMarshaler.indent = " "
}
}

// NewJSONMarshalerUseProtoNames returns a new Marshaler for JSON using the proto names for keys.
//
// This has the potential to be unstable over time.
// resolver can be nil if unknown and are only needed for extensions.
func NewJSONMarshalerUseProtoNames(resolver Resolver) Marshaler {
return newJSONMarshaler(resolver, "", true)
// JSONMarshalerWithUseProtoNames says to use an use proto names.
func JSONMarshalerWithUseProtoNames() JSONMarshalerOption {
return func(jsonMarshaler *jsonMarshaler) {
jsonMarshaler.useProtoNames = true
}
}

// JSONMarshalerWithEmitUnpopulated says to emit unpopulated values
func JSONMarshalerWithEmitUnpopulated() JSONMarshalerOption {
return func(jsonMarshaler *jsonMarshaler) {
jsonMarshaler.emitUnpopulated = true
}
}

// Unmarshaler unmarshals Messages.
Expand Down
4 changes: 2 additions & 2 deletions private/pkg/prototesting/prototesting.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,15 @@ func DiffFileDescriptorSetsJSON(
if err != nil {
return "", err
}
oneData, err := protoencoding.NewJSONMarshalerIndent(oneResolver).Marshal(one)
oneData, err := protoencoding.NewJSONMarshaler(oneResolver, protoencoding.JSONMarshalerWithIndent()).Marshal(one)
if err != nil {
return "", err
}
twoResolver, err := protoencoding.NewResolver(protodescriptor.FileDescriptorsForFileDescriptorSet(two)...)
if err != nil {
return "", err
}
twoData, err := protoencoding.NewJSONMarshalerIndent(twoResolver).Marshal(two)
twoData, err := protoencoding.NewJSONMarshaler(twoResolver, protoencoding.JSONMarshalerWithIndent()).Marshal(two)
if err != nil {
return "", err
}
Expand Down

0 comments on commit 0e08a8f

Please sign in to comment.