From 91fad07bb77daa726a3e47bc876da0caf56b38fd Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 11 Sep 2023 19:03:48 +0200 Subject: [PATCH] feat(x/tx): add indent option to encoder (#17600) --- x/tx/CHANGELOG.md | 7 +++ x/tx/signing/aminojson/json_marshal.go | 33 +++++++++++- x/tx/signing/aminojson/json_marshal_test.go | 60 ++++++++++++++++++++- 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/x/tx/CHANGELOG.md b/x/tx/CHANGELOG.md index 36246c97eb..ec4885e5ba 100644 --- a/x/tx/CHANGELOG.md +++ b/x/tx/CHANGELOG.md @@ -31,6 +31,13 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +## v0.10.0 + +### Features + +* [#17600](https://github.com/cosmos/cosmos-sdk/pull/17600) Add encoder `DefineScalarEncoding` method for defining custom scalar encodings. +* [#17600](https://github.com/cosmos/cosmos-sdk/pull/17600) Add indent option to encoder. + ## v0.9.1 ### Improvements diff --git a/x/tx/signing/aminojson/json_marshal.go b/x/tx/signing/aminojson/json_marshal.go index 4cd8c1604b..f6698c4757 100644 --- a/x/tx/signing/aminojson/json_marshal.go +++ b/x/tx/signing/aminojson/json_marshal.go @@ -23,7 +23,10 @@ type FieldEncoder func(*Encoder, protoreflect.Value, io.Writer) error // EncoderOptions are options for creating a new Encoder. type EncoderOptions struct { - // DonotSortFields when set turns off sorting of field names. + // Indent can only be composed of space or tab characters. + // It defines the indentation used for each level of indentation. + Indent string + // DoNotSortFields when set turns off sorting of field names. DoNotSortFields bool // TypeResolver is used to resolve protobuf message types by TypeURL when marshaling any packed messages. TypeResolver signing.TypeResolver @@ -40,6 +43,7 @@ type Encoder struct { fileResolver signing.ProtoFileResolver typeResolver protoregistry.MessageTypeResolver doNotSortFields bool + indent string } // NewEncoder returns a new Encoder capable of serializing protobuf messages to JSON using the Amino JSON encoding @@ -67,6 +71,7 @@ func NewEncoder(options EncoderOptions) Encoder { fileResolver: options.FileResolver, typeResolver: options.TypeResolver, doNotSortFields: options.DoNotSortFields, + indent: options.Indent, } return enc } @@ -109,10 +114,36 @@ func (enc Encoder) DefineFieldEncoding(name string, encoder FieldEncoder) Encode return enc } +// DefineScalarEncoding defines a custom encoding for a protobuf scalar field. The `name` field must match a usage of +// an (cosmos_proto.scalar) option in the protobuf message as in the following example. This encoding will be used +// instead of the default encoding for all usages of the tagged field. +// +// message Balance { +// string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +// ... +// } +func (enc Encoder) DefineScalarEncoding(name string, encoder FieldEncoder) Encoder { + if enc.scalarEncoders == nil { + enc.scalarEncoders = map[string]FieldEncoder{} + } + enc.scalarEncoders[name] = encoder + return enc +} + // Marshal serializes a protobuf message to JSON. func (enc Encoder) Marshal(message proto.Message) ([]byte, error) { buf := &bytes.Buffer{} err := enc.beginMarshal(message.ProtoReflect(), buf) + + if enc.indent != "" { + indentBuf := &bytes.Buffer{} + if err := json.Indent(indentBuf, buf.Bytes(), "", enc.indent); err != nil { + return nil, err + } + + return indentBuf.Bytes(), err + } + return buf.Bytes(), err } diff --git a/x/tx/signing/aminojson/json_marshal_test.go b/x/tx/signing/aminojson/json_marshal_test.go index 4c2d6d35d9..8937f8a9da 100644 --- a/x/tx/signing/aminojson/json_marshal_test.go +++ b/x/tx/signing/aminojson/json_marshal_test.go @@ -174,6 +174,64 @@ func TestDynamicPb(t *testing.T) { require.NoError(t, err) dynamicBz, err := encoder.Marshal(dynamicMsg) require.NoError(t, err) - fmt.Printf("dynamicBz: %s\n", string(dynamicBz)) require.Equal(t, string(bz), string(dynamicBz)) } + +func TestIndent(t *testing.T) { + encoder := aminojson.NewEncoder(aminojson.EncoderOptions{Indent: " "}) + + msg := &testpb.ABitOfEverything{ + Message: &testpb.NestedMessage{ + Foo: "test", + Bar: 0, // this is the default value and should be omitted from output + }, + Enum: testpb.AnEnum_ONE, + Repeated: []int32{3, -7, 2, 6, 4}, + Str: `abcxyz"foo"def`, + Bool: true, + Bytes: []byte{0, 1, 2, 3}, + I32: -15, + F32: 1001, + U32: 1200, + Si32: -376, + Sf32: -1000, + I64: 14578294827584932, + F64: 9572348124213523654, + U64: 4759492485, + Si64: -59268425823934, + Sf64: -659101379604211154, + } + + bz, err := encoder.Marshal(msg) + require.NoError(t, err) + fmt.Println(string(bz)) + require.Equal(t, `{ + "type": "ABitOfEverything", + "value": { + "bool": true, + "bytes": "AAECAw==", + "enum": 1, + "f32": 1001, + "f64": "9572348124213523654", + "i32": -15, + "i64": "14578294827584932", + "message": { + "foo": "test" + }, + "repeated": [ + 3, + -7, + 2, + 6, + 4 + ], + "sf32": -1000, + "sf64": "-659101379604211154", + "si32": -376, + "si64": "-59268425823934", + "str": "abcxyz\"foo\"def", + "u32": 1200, + "u64": "4759492485" + } +}`, string(bz)) +}