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

Add emit-defaults flag to buf curl #2132

Merged
merged 6 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- Add `--emit-defaults` flag to `buf curl` to emit default values in JSON-encoded responses.
- 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
28 changes: 15 additions & 13 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
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
14 changes: 13 additions & 1 deletion private/buf/cmd/buf/command/curl/curl.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ const (
dataFlagShortName = "d"
outputFlagName = "output"
outputFlagShortName = "o"

// Other flags
emitDefaultsFlagName = "emit-defaults"
bufdev marked this conversation as resolved.
Show resolved Hide resolved
)

// NewCommand returns a new Command.
Expand Down Expand Up @@ -225,6 +228,9 @@ type flags struct {
Data string
Output string

// Other options
EmitDefaults bool
bufdev marked this conversation as resolved.
Show resolved Hide resolved

// so we can inquire about which flags present on command-line
// TODO: ideally we'd use cobra directly instead of having the appcmd wrapper,
// which prevents a lot of basic functionality by not exposing many cobra features
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