diff --git a/CHANGELOG.md b/CHANGELOG.md index a5f190639a..3165e1afbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ ## [Unreleased] -- No changes yet. +- Add `txtpb` format to handle the Protobuf text format. and automatically recognize + `.txtpb` files as Protobuf text files. The `txtpb` format can now be used with + all `buf` commands that take images as input or output, such as `build`, `convert`, + and `curl`. ## [v1.24.0] - 2023-07-13 diff --git a/private/buf/bufconvert/bufconvert.go b/private/buf/bufconvert/bufconvert.go index 6b5235bb5a..07108874d9 100644 --- a/private/buf/bufconvert/bufconvert.go +++ b/private/buf/bufconvert/bufconvert.go @@ -32,10 +32,14 @@ const ( MessageEncodingBinpb MessageEncoding = iota + 1 // MessageEncodingJSON is the JSON image encoding. MessageEncodingJSON + // MessageEncodingTextpb is the protobuf text image encoding. + MessageEncodingTextpb // formatBinpb is the binary format. formatBinpb = "binpb" // formatJSON is the JSON format. formatJSON = "json" + // formatTxtpb is the protobuf text format. + formatTxtpb = "txtpb" // formatBin is the binary format's old form, now deprecated. formatBin = "bin" @@ -50,6 +54,7 @@ var ( messageEncodingFormats = []string{ formatBinpb, formatJSON, + formatTxtpb, } ) @@ -112,6 +117,8 @@ func parseMessageEncodingExt(ext string, defaultEncoding MessageEncoding) Messag return MessageEncodingBinpb case formatJSON: return MessageEncodingJSON + case formatTxtpb: + return MessageEncodingTextpb default: return defaultEncoding } @@ -123,6 +130,8 @@ func parseMessageEncodingFormat(format string) (MessageEncoding, error) { return MessageEncodingBinpb, nil case formatJSON: return MessageEncodingJSON, nil + case formatTxtpb: + return MessageEncodingTextpb, nil default: return 0, fmt.Errorf("invalid format for message: %q", format) } diff --git a/private/buf/buffetch/buffetch.go b/private/buf/buffetch/buffetch.go index be239c0228..91b7d94c5c 100644 --- a/private/buf/buffetch/buffetch.go +++ b/private/buf/buffetch/buffetch.go @@ -34,6 +34,8 @@ const ( ImageEncodingBin ImageEncoding = iota + 1 // ImageEncodingJSON is the JSON image encoding. ImageEncodingJSON + // ImageEncodingTxtpb is the text protobuf image encoding. + ImageEncodingTxtpb ) var ( diff --git a/private/buf/buffetch/format.go b/private/buf/buffetch/format.go index 73e9dfeb29..8a3079b8ed 100644 --- a/private/buf/buffetch/format.go +++ b/private/buf/buffetch/format.go @@ -15,8 +15,10 @@ package buffetch const ( - // formatBinpb is the binary format. + // formatBinpb is the protobuf binary format. formatBinpb = "binpb" + // formatTxtpb is the protobuf text format. + formatTxtpb = "txtpb" // formatDir is the directory format. formatDir = "dir" // formatGit is the git format. @@ -50,11 +52,13 @@ var ( formatBingz, formatJSON, formatJSONGZ, + formatTxtpb, } // sorted imageFormatsNotDeprecated = []string{ formatBinpb, formatJSON, + formatTxtpb, } // sorted sourceFormats = []string{ @@ -119,6 +123,7 @@ var ( formatProtoFile, formatTar, formatTargz, + formatTxtpb, formatZip, } // sorted @@ -130,6 +135,7 @@ var ( formatMod, formatProtoFile, formatTar, + formatTxtpb, formatZip, } diff --git a/private/buf/buffetch/ref_parser.go b/private/buf/buffetch/ref_parser.go index ec2d9491df..048f8dbea8 100644 --- a/private/buf/buffetch/ref_parser.go +++ b/private/buf/buffetch/ref_parser.go @@ -52,6 +52,7 @@ func newRefParser(logger *zap.Logger) *refParser { internal.WithSingleFormat(formatBin), internal.WithSingleFormat(formatBinpb), internal.WithSingleFormat(formatJSON), + internal.WithSingleFormat(formatTxtpb), internal.WithSingleFormat( formatBingz, internal.WithSingleDefaultCompressionType( @@ -96,6 +97,7 @@ func newImageRefParser(logger *zap.Logger) *refParser { internal.WithSingleFormat(formatBin), internal.WithSingleFormat(formatBinpb), internal.WithSingleFormat(formatJSON), + internal.WithSingleFormat(formatTxtpb), internal.WithSingleFormat( formatBingz, internal.WithSingleDefaultCompressionType( @@ -372,6 +374,8 @@ func newRawRefProcessor() func(*internal.RawRef) error { format = formatJSON case ".tar": format = formatTar + case ".txtpb": + format = formatTxtpb case ".zip": format = formatZip case ".gz": @@ -383,6 +387,8 @@ func newRawRefProcessor() func(*internal.RawRef) error { format = formatJSON case ".tar": format = formatTar + case ".txtpb": + format = formatTxtpb default: return fmt.Errorf("path %q had .gz extension with unknown format", rawRef.Path) } @@ -395,6 +401,8 @@ func newRawRefProcessor() func(*internal.RawRef) error { format = formatJSON case ".tar": format = formatTar + case ".txtpb": + format = formatTxtpb default: return fmt.Errorf("path %q had .zst extension with unknown format", rawRef.Path) } @@ -520,6 +528,8 @@ func processRawRefImage(rawRef *internal.RawRef) error { format = formatBinpb case ".json": format = formatJSON + case ".textpb": + format = formatTxtpb case ".gz": compressionType = internal.CompressionTypeGzip switch filepath.Ext(strings.TrimSuffix(rawRef.Path, filepath.Ext(rawRef.Path))) { @@ -527,6 +537,8 @@ func processRawRefImage(rawRef *internal.RawRef) error { format = formatBinpb case ".json": format = formatJSON + case ".textpb": + format = formatTxtpb default: return fmt.Errorf("path %q had .gz extension with unknown format", rawRef.Path) } @@ -537,6 +549,8 @@ func processRawRefImage(rawRef *internal.RawRef) error { format = formatBinpb case ".json": format = formatJSON + case ".textpb": + format = formatTxtpb default: return fmt.Errorf("path %q had .zst extension with unknown format", rawRef.Path) } @@ -560,6 +574,8 @@ func parseImageEncoding(format string) (ImageEncoding, error) { return ImageEncodingBin, nil case formatJSON, formatJSONGZ: return ImageEncodingJSON, nil + case formatTxtpb: + return ImageEncodingTxtpb, nil default: return 0, fmt.Errorf("invalid format for image: %q", format) } diff --git a/private/buf/buffetch/ref_parser_test.go b/private/buf/buffetch/ref_parser_test.go index 5793ac59f9..b00477de52 100644 --- a/private/buf/buffetch/ref_parser_test.go +++ b/private/buf/buffetch/ref_parser_test.go @@ -548,6 +548,46 @@ func TestGetParsedRefSuccess(t *testing.T) { ), "path/to/file.json.gz#compression=gzip", ) + testGetParsedRefSuccess( + t, + internal.NewDirectParsedSingleRef( + formatTxtpb, + "path/to/file.txtpb", + internal.FileSchemeLocal, + internal.CompressionTypeNone, + ), + "path/to/file.txtpb", + ) + testGetParsedRefSuccess( + t, + internal.NewDirectParsedSingleRef( + formatTxtpb, + "path/to/file.txtpb.gz", + internal.FileSchemeLocal, + internal.CompressionTypeGzip, + ), + "path/to/file.txtpb.gz", + ) + testGetParsedRefSuccess( + t, + internal.NewDirectParsedSingleRef( + formatTxtpb, + "path/to/file.txtpb.gz", + internal.FileSchemeLocal, + internal.CompressionTypeNone, + ), + "path/to/file.txtpb.gz#compression=none", + ) + testGetParsedRefSuccess( + t, + internal.NewDirectParsedSingleRef( + formatTxtpb, + "path/to/file.txtpb.gz", + internal.FileSchemeLocal, + internal.CompressionTypeGzip, + ), + "path/to/file.txtpb.gz#compression=gzip", + ) testGetParsedRefSuccess( t, internal.NewDirectParsedSingleRef( @@ -568,6 +608,16 @@ func TestGetParsedRefSuccess(t *testing.T) { ), "-#format=json", ) + testGetParsedRefSuccess( + t, + internal.NewDirectParsedSingleRef( + formatTxtpb, + "", + internal.FileSchemeStdio, + internal.CompressionTypeNone, + ), + "-#format=txtpb", + ) testGetParsedRefSuccess( t, internal.NewDirectParsedSingleRef( diff --git a/private/buf/bufwire/image_reader.go b/private/buf/bufwire/image_reader.go index 326a99016b..01d866eac1 100644 --- a/private/buf/bufwire/image_reader.go +++ b/private/buf/bufwire/image_reader.go @@ -97,31 +97,11 @@ func (i *imageReader) GetImage( } span.End() case buffetch.ImageEncodingJSON: - firstProtoImage := &imagev1.Image{} - _, span := i.tracer.Start(ctx, "first_json_unmarshal") - if err := protoencoding.NewJSONUnmarshaler(nil).Unmarshal(data, firstProtoImage); err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - span.End() - return nil, fmt.Errorf("could not unmarshal image: %v", err) - } - // TODO right now, NewResolver sets AllowUnresolvable to true all the time - // we want to make this into a check, and we verify if we need this for the individual command - span.End() - _, newResolverSpan := i.tracer.Start(ctx, "new_resolver") - resolver, err := protoencoding.NewResolver( - bufimage.ProtoImageToFileDescriptors( - firstProtoImage, - )..., - ) + resolver, err := i.bootstrapResolver(ctx, protoencoding.NewJSONUnmarshaler(nil), data) if err != nil { - newResolverSpan.RecordError(err) - newResolverSpan.SetStatus(codes.Error, err.Error()) - newResolverSpan.End() return nil, err } - newResolverSpan.End() - _, jsonUnmarshalSpan := i.tracer.Start(ctx, "second_json_unmarshal") + _, jsonUnmarshalSpan := i.tracer.Start(ctx, "json_unmarshal") if err := protoencoding.NewJSONUnmarshaler(resolver).Unmarshal(data, protoImage); err != nil { jsonUnmarshalSpan.RecordError(err) jsonUnmarshalSpan.SetStatus(codes.Error, err.Error()) @@ -131,6 +111,21 @@ func (i *imageReader) GetImage( jsonUnmarshalSpan.End() // we've already re-parsed, by unmarshalling 2x above imageFromProtoOptions = append(imageFromProtoOptions, bufimage.WithNoReparse()) + case buffetch.ImageEncodingTxtpb: + resolver, err := i.bootstrapResolver(ctx, protoencoding.NewTxtpbUnmarshaler(nil), data) + if err != nil { + return nil, err + } + _, txtpbUnmarshalSpan := i.tracer.Start(ctx, "txtpb_unmarshal") + if err := protoencoding.NewTxtpbUnmarshaler(resolver).Unmarshal(data, protoImage); err != nil { + txtpbUnmarshalSpan.RecordError(err) + txtpbUnmarshalSpan.SetStatus(codes.Error, err.Error()) + txtpbUnmarshalSpan.End() + return nil, fmt.Errorf("could not unmarshal image: %v", err) + } + txtpbUnmarshalSpan.End() + // we've already re-parsed, by unmarshalling 2x above + imageFromProtoOptions = append(imageFromProtoOptions, bufimage.WithNoReparse()) default: return nil, fmt.Errorf("unknown image encoding: %v", imageEncoding) } @@ -168,3 +163,33 @@ func (i *imageReader) GetImage( } return bufimage.ImageWithOnlyPaths(image, imagePaths, excludePaths) } + +func (i *imageReader) bootstrapResolver( + ctx context.Context, + unresolving protoencoding.Unmarshaler, + data []byte, +) (protoencoding.Resolver, error) { + firstProtoImage := &imagev1.Image{} + _, span := i.tracer.Start(ctx, "bootstrap_unmarshal") + if err := unresolving.Unmarshal(data, firstProtoImage); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + span.End() + return nil, fmt.Errorf("could not unmarshal image: %v", err) + } + span.End() + _, newResolverSpan := i.tracer.Start(ctx, "new_resolver") + resolver, err := protoencoding.NewResolver( + bufimage.ProtoImageToFileDescriptors( + firstProtoImage, + )..., + ) + if err != nil { + newResolverSpan.RecordError(err) + newResolverSpan.SetStatus(codes.Error, err.Error()) + newResolverSpan.End() + return nil, err + } + newResolverSpan.End() + return resolver, nil +} diff --git a/private/buf/bufwire/image_writer.go b/private/buf/bufwire/image_writer.go index 3d92db6851..d046afc62b 100644 --- a/private/buf/bufwire/image_writer.go +++ b/private/buf/bufwire/image_writer.go @@ -117,6 +117,17 @@ func (i *imageWriter) imageMarshal( return nil, err } return protoencoding.NewJSONMarshaler(resolver).Marshal(message) + case buffetch.ImageEncodingTxtpb: + // TODO: verify that image is complete + resolver, err := protoencoding.NewResolver( + bufimage.ImageToFileDescriptors( + image, + )..., + ) + if err != nil { + return nil, err + } + return protoencoding.NewTxtpbMarshaler(resolver).Marshal(message) default: return nil, fmt.Errorf("unknown image encoding: %v", imageEncoding) } diff --git a/private/buf/bufwire/proto_encoding_reader.go b/private/buf/bufwire/proto_encoding_reader.go index c326a3ed9f..04818430f5 100644 --- a/private/buf/bufwire/proto_encoding_reader.go +++ b/private/buf/bufwire/proto_encoding_reader.go @@ -77,6 +77,8 @@ func (p *protoEncodingReader) GetMessage( unmarshaler = protoencoding.NewWireUnmarshaler(resolver) case bufconvert.MessageEncodingJSON: unmarshaler = protoencoding.NewJSONUnmarshaler(resolver) + case bufconvert.MessageEncodingTextpb: + unmarshaler = protoencoding.NewTxtpbUnmarshaler(resolver) default: return nil, errors.New("unknown message encoding type") } diff --git a/private/buf/bufwire/proto_encoding_writer.go b/private/buf/bufwire/proto_encoding_writer.go index 2bc08ff5cf..0cd83eac70 100644 --- a/private/buf/bufwire/proto_encoding_writer.go +++ b/private/buf/bufwire/proto_encoding_writer.go @@ -65,6 +65,8 @@ func (p *protoEncodingWriter) PutMessage( marshaler = protoencoding.NewWireMarshaler() case bufconvert.MessageEncodingJSON: marshaler = protoencoding.NewJSONMarshaler(resolver) + case bufconvert.MessageEncodingTextpb: + marshaler = protoencoding.NewTxtpbMarshaler(resolver) default: return errors.New("unknown message encoding type") } diff --git a/private/buf/cmd/buf/buf_test.go b/private/buf/cmd/buf/buf_test.go index 10b5310aaf..4688641852 100644 --- a/private/buf/cmd/buf/buf_test.go +++ b/private/buf/cmd/buf/buf_test.go @@ -2034,6 +2034,22 @@ func TestConvert(t *testing.T) { "-#format=binpb", ) }) + t.Run("stdin-image-txtpb-to-binpb", func(t *testing.T) { + t.Parallel() + file, err := os.Open(convertTestDataDir + "/bin_json/image.txtpb") + require.NoError(t, err) + testRunStdoutFile(t, + file, + 0, + convertTestDataDir+"/bin_json/payload.binpb", + "convert", + "--type=buf.Foo", + "-#format=txtpb", + "--from="+convertTestDataDir+"/bin_json/payload.txtpb", + "--to", + "-#format=binpb", + ) + }) } func TestFormat(t *testing.T) { diff --git a/private/buf/cmd/buf/command/convert/convert.go b/private/buf/cmd/buf/command/convert/convert.go index 52c9fa80d7..ec7473dbe1 100644 --- a/private/buf/cmd/buf/command/convert/convert.go +++ b/private/buf/cmd/buf/command/convert/convert.go @@ -46,7 +46,7 @@ func NewCommand( flags := newFlags() return &appcmd.Command{ Use: name + " ", - Short: "Convert a message from binary to JSON or vice versa", + Short: "Convert a message between binary, text, or JSON", Long: ` Use an input proto to interpret a proto/json message and convert it to a different format. @@ -232,6 +232,8 @@ func inverseEncoding(encoding bufconvert.MessageEncoding) (bufconvert.MessageEnc return bufconvert.MessageEncodingJSON, nil case bufconvert.MessageEncodingJSON: return bufconvert.MessageEncodingBinpb, nil + case bufconvert.MessageEncodingTextpb: + return bufconvert.MessageEncodingBinpb, nil default: return 0, fmt.Errorf("unknown message encoding %v", encoding) } diff --git a/private/buf/cmd/buf/command/convert/convert_test.go b/private/buf/cmd/buf/command/convert/convert_test.go index 0e48d73240..6222f9bcb6 100644 --- a/private/buf/cmd/buf/command/convert/convert_test.go +++ b/private/buf/cmd/buf/command/convert/convert_test.go @@ -57,6 +57,23 @@ func TestConvertDir(t *testing.T) { "testdata/convert/bin_json/payload.binpb", ) }) + t.Run("default-input-txtpb", func(t *testing.T) { + t.Parallel() + appcmdtesting.RunCommandExitCodeStdout( + t, + cmd, + 0, + `{"one":"55"}`, + nil, + nil, + "--type", + "buf.Foo", + "--from", + "testdata/convert/bin_json/payload.txtpb", + "--to", + "-#format=json", + ) + }) t.Run("from-stdin-bin", func(t *testing.T) { t.Parallel() appcmdtesting.RunCommandExitCodeStdoutStdinFile( @@ -89,6 +106,24 @@ func TestConvertDir(t *testing.T) { "-#format=binpb", ) }) + t.Run("from-stdin-txtpb", func(t *testing.T) { + t.Parallel() + appcmdtesting.RunCommandExitCodeStdoutStdinFile( + t, + cmd, + 0, + `{"one":"55"}`, + nil, + "testdata/convert/bin_json/payload.txtpb", + + "--type", + "buf.Foo", + "--from", + "-#format=txtpb", + "--to", + "-#format=json", + ) + }) t.Run("discarded-stdin", func(t *testing.T) { t.Parallel() appcmdtesting.RunCommandExitCodeStdout( @@ -134,6 +169,23 @@ func TestConvertDir(t *testing.T) { "testdata/convert/bin_json/duration.binpb", ) }) + t.Run("wellknowntype-txtpb", func(t *testing.T) { + t.Parallel() + appcmdtesting.RunCommandExitCodeStdout( + t, + cmd, + 0, + `"3600s"`, + nil, + nil, + "--type", + "google.protobuf.Duration", + "--from", + "testdata/convert/bin_json/duration.txtpb", + "--to", + "-#format=json", + ) + }) t.Run("wellknowntype-format-bin", func(t *testing.T) { t.Parallel() appcmdtesting.RunCommandExitCodeStdoutFile( diff --git a/private/buf/cmd/buf/command/convert/testdata/convert/bin_json/duration.txtpb b/private/buf/cmd/buf/command/convert/testdata/convert/bin_json/duration.txtpb new file mode 100644 index 0000000000..c1ea275231 --- /dev/null +++ b/private/buf/cmd/buf/command/convert/testdata/convert/bin_json/duration.txtpb @@ -0,0 +1 @@ +seconds: 3600 diff --git a/private/buf/cmd/buf/command/convert/testdata/convert/bin_json/image.txtpb b/private/buf/cmd/buf/command/convert/testdata/convert/bin_json/image.txtpb new file mode 100644 index 0000000000..51d643fecc --- /dev/null +++ b/private/buf/cmd/buf/command/convert/testdata/convert/bin_json/image.txtpb @@ -0,0 +1,94 @@ +file: { + name: "buf.proto" + package: "buf" + message_type: { + name: "Foo" + field: { + name: "one" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_INT64 + json_name: "one" + } + } + source_code_info: { + location: { + span: 0 + span: 0 + span: 6 + span: 1 + } + location: { + path: 12 + span: 0 + span: 0 + span: 18 + } + location: { + path: 2 + span: 2 + span: 0 + span: 12 + } + location: { + path: 4 + path: 0 + span: 4 + span: 0 + span: 6 + span: 1 + } + location: { + path: 4 + path: 0 + path: 1 + span: 4 + span: 8 + span: 11 + } + location: { + path: 4 + path: 0 + path: 2 + path: 0 + span: 5 + span: 2 + span: 16 + } + location: { + path: 4 + path: 0 + path: 2 + path: 0 + path: 5 + span: 5 + span: 2 + span: 7 + } + location: { + path: 4 + path: 0 + path: 2 + path: 0 + path: 1 + span: 5 + span: 8 + span: 11 + } + location: { + path: 4 + path: 0 + path: 2 + path: 0 + path: 3 + span: 5 + span: 14 + span: 15 + } + } + syntax: "proto3" + buf_extension: { + is_import: false + is_syntax_unspecified: false + } +} diff --git a/private/buf/cmd/buf/command/convert/testdata/convert/bin_json/payload.txtpb b/private/buf/cmd/buf/command/convert/testdata/convert/bin_json/payload.txtpb new file mode 100644 index 0000000000..b8bd3287bc --- /dev/null +++ b/private/buf/cmd/buf/command/convert/testdata/convert/bin_json/payload.txtpb @@ -0,0 +1 @@ +one: 55 diff --git a/private/pkg/protoencoding/protoencoding.go b/private/pkg/protoencoding/protoencoding.go index 20b8119d74..e1a76a1c9d 100644 --- a/private/pkg/protoencoding/protoencoding.go +++ b/private/pkg/protoencoding/protoencoding.go @@ -95,6 +95,13 @@ func JSONMarshalerWithEmitUnpopulated() JSONMarshalerOption { } } +// NewTxtpbMarshaler returns a new Marshaler for txtpb. +// +// resolver can be nil if unknown and are only needed for extensions. +func NewTxtpbMarshaler(resolver Resolver) Marshaler { + return newTxtpbMarshaler(resolver) +} + // Unmarshaler unmarshals Messages. type Unmarshaler interface { Unmarshal(data []byte, message proto.Message) error @@ -113,3 +120,10 @@ func NewWireUnmarshaler(resolver Resolver) Unmarshaler { func NewJSONUnmarshaler(resolver Resolver) Unmarshaler { return newJSONUnmarshaler(resolver) } + +// NewTxtpbUnmarshaler returns a new Unmarshaler for txtpb. +// +// resolver can be nil if unknown and are only needed for extensions. +func NewTxtpbUnmarshaler(resolver Resolver) Unmarshaler { + return newTxtpbUnmarshaler(resolver) +} diff --git a/private/pkg/protoencoding/txtpb_marshaler.go b/private/pkg/protoencoding/txtpb_marshaler.go new file mode 100644 index 0000000000..804e6647c1 --- /dev/null +++ b/private/pkg/protoencoding/txtpb_marshaler.go @@ -0,0 +1,40 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package protoencoding + +import ( + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" +) + +type txtpbMarshaler struct { + resolver Resolver +} + +func newTxtpbMarshaler(resolver Resolver) Marshaler { + return &txtpbMarshaler{ + resolver: resolver, + } +} + +func (m *txtpbMarshaler) Marshal(message proto.Message) ([]byte, error) { + options := prototext.MarshalOptions{ + Resolver: m.resolver, + // TODO: make this an option + Multiline: true, + Indent: " ", + } + return options.Marshal(message) +} diff --git a/private/pkg/protoencoding/txtpb_unmarshaler.go b/private/pkg/protoencoding/txtpb_unmarshaler.go new file mode 100644 index 0000000000..c8f0d78fb3 --- /dev/null +++ b/private/pkg/protoencoding/txtpb_unmarshaler.go @@ -0,0 +1,39 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package protoencoding + +import ( + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" +) + +type txtpbUnmarshaler struct { + resolver Resolver +} + +func newTxtpbUnmarshaler(resolver Resolver) Unmarshaler { + return &txtpbUnmarshaler{ + resolver: resolver, + } +} + +func (m *txtpbUnmarshaler) Unmarshal(data []byte, message proto.Message) error { + options := prototext.UnmarshalOptions{ + Resolver: m.resolver, + // TODO: make this an option + DiscardUnknown: true, + } + return options.Unmarshal(data, message) +}