feat: value renderer for timestamp protos (#12860)

## Description

Closes: #12709



Part of Sign Mode Textual (ADR 050) implementation.

Renders Timestamp messages as RFC 3339 (simplified ISO 8601).

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [x] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting))
- [x] provided a link to the relevant issue or specification
- [x] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/building-modules)
- [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [x] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [x] updated the relevant documentation or specification
- [x] reviewed "Files changed" and left comments if necessary
- [x] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
This commit is contained in:
Jim Larson 2022-08-23 12:58:29 -07:00 committed by GitHub
parent 2e1fb485cd
commit ef4ad67c9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 462 additions and 90 deletions

View File

@ -203,7 +203,20 @@ Object: /cosmos.gov.v1.Vote
### `google.protobuf.Timestamp`
Rendered as either ISO8601 (`2021-01-01T12:00:00Z`).
Rendered using [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339) (a
simplification of ISO 8601), which is the current recommendation for portable
time values. The rendering always uses "Z" (UTC) as the timezone. It uses only
the necessary fractional digits of a second, omitting the fractional part
entirely if the timestamp has no fractional seconds. (The resulting timestamps
are not automatically sortable by standard lexicographic order, but we favor
the legibility of the shorter string.)
#### Examples
The timestamp with 1136214245 seconds and 700000000 nanoseconds is rendered
as `2006-01-02T15:04:05.7Z`.
The timestamp with 1136214245 seconds and zero nanoseconds is rendered
as `2006-01-02T15:04:05Z`.
### `google.protobuf.Duration` (TODO)

View File

@ -0,0 +1,82 @@
[
{
"proto": {"seconds": 1136214245},
"text": "2006-01-02T15:04:05Z"
},
{
"proto": {"seconds": 1136214245, "nanos": 123456789},
"text": "2006-01-02T15:04:05.123456789Z"
},
{
"proto": {"seconds": 1136214245, "nanos": 123000000},
"text": "2006-01-02T15:04:05.123Z"
},
{ "text": "", "error": true },
{ "text": " ", "error": true },
{ "text": "garbage", "error": true },
{ "text": "11/30/2007", "error": true },
{
"proto": {"seconds": 0, "nanos": 0},
"text": "1970-01-01T00:00:00Z"
},
{
"proto": {"seconds": 0, "nanos": 1},
"text": "1970-01-01T00:00:00.000000001Z"
},
{
"proto": {"seconds": 0, "nanos": 10},
"text": "1970-01-01T00:00:00.00000001Z"
},
{
"proto": {"seconds": 0, "nanos": 100},
"text": "1970-01-01T00:00:00.0000001Z"
},
{
"proto": {"seconds": 0, "nanos": 1000},
"text": "1970-01-01T00:00:00.000001Z"
},
{
"proto": {"seconds": 0, "nanos": 10000},
"text": "1970-01-01T00:00:00.00001Z"
},
{
"proto": {"seconds": 0, "nanos": 100000},
"text": "1970-01-01T00:00:00.0001Z"
},
{
"proto": {"seconds": 0, "nanos": 1000000},
"text": "1970-01-01T00:00:00.001Z"
},
{
"proto": {"seconds": 0, "nanos": 10000000},
"text": "1970-01-01T00:00:00.01Z"
},
{
"proto": {"seconds": 0, "nanos": 100000000},
"text": "1970-01-01T00:00:00.1Z"
},
{
"proto": {"seconds": 0, "nanos": 999999999},
"text": "1970-01-01T00:00:00.999999999Z"
},
{
"proto": {"seconds": 1, "nanos": 0},
"text": "1970-01-01T00:00:01Z"
},
{
"proto": {"seconds": 2, "nanos": 0},
"text": "1970-01-01T00:00:02Z"
},
{
"proto": {"seconds": 2, "nanos": 6},
"text": "1970-01-01T00:00:02.000000006Z"
},
{
"proto": {"seconds": 1657797740, "nanos": 983000000},
"text": "2022-07-14T11:22:20.983Z"
},
{
"proto": {"seconds": -1, "nanos": 0},
"text": "1969-12-31T23:59:59Z"
}
]

View File

@ -3,6 +3,7 @@ syntax = "proto3";
option go_package = "cosmossdk.io/tx/textual/internal/testpb";
import "google/protobuf/descriptor.proto";
import "google/protobuf/timestamp.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/base/v1beta1/coin.proto";
@ -22,6 +23,7 @@ message A {
cosmos.base.v1beta1.Coin COIN = 7;
repeated cosmos.base.v1beta1.Coin COINS = 8;
bytes BYTES = 9;
google.protobuf.Timestamp TIMESTAMP = 10;
}
// B contains fields that are not parseable by SIGN_MODE_TEXTUAL, some fields

View File

@ -11,6 +11,7 @@ import (
protoiface "google.golang.org/protobuf/runtime/protoiface"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
_ "google.golang.org/protobuf/types/descriptorpb"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
io "io"
math "math"
reflect "reflect"
@ -70,16 +71,17 @@ func (x *_A_8_list) IsValid() bool {
}
var (
md_A protoreflect.MessageDescriptor
fd_A_UINT32 protoreflect.FieldDescriptor
fd_A_UINT64 protoreflect.FieldDescriptor
fd_A_INT32 protoreflect.FieldDescriptor
fd_A_INT64 protoreflect.FieldDescriptor
fd_A_SDKINT protoreflect.FieldDescriptor
fd_A_SDKDEC protoreflect.FieldDescriptor
fd_A_COIN protoreflect.FieldDescriptor
fd_A_COINS protoreflect.FieldDescriptor
fd_A_BYTES protoreflect.FieldDescriptor
md_A protoreflect.MessageDescriptor
fd_A_UINT32 protoreflect.FieldDescriptor
fd_A_UINT64 protoreflect.FieldDescriptor
fd_A_INT32 protoreflect.FieldDescriptor
fd_A_INT64 protoreflect.FieldDescriptor
fd_A_SDKINT protoreflect.FieldDescriptor
fd_A_SDKDEC protoreflect.FieldDescriptor
fd_A_COIN protoreflect.FieldDescriptor
fd_A_COINS protoreflect.FieldDescriptor
fd_A_BYTES protoreflect.FieldDescriptor
fd_A_TIMESTAMP protoreflect.FieldDescriptor
)
func init() {
@ -94,6 +96,7 @@ func init() {
fd_A_COIN = md_A.Fields().ByName("COIN")
fd_A_COINS = md_A.Fields().ByName("COINS")
fd_A_BYTES = md_A.Fields().ByName("BYTES")
fd_A_TIMESTAMP = md_A.Fields().ByName("TIMESTAMP")
}
var _ protoreflect.Message = (*fastReflection_A)(nil)
@ -215,6 +218,12 @@ func (x *fastReflection_A) Range(f func(protoreflect.FieldDescriptor, protorefle
return
}
}
if x.TIMESTAMP != nil {
value := protoreflect.ValueOfMessage(x.TIMESTAMP.ProtoReflect())
if !f(fd_A_TIMESTAMP, value) {
return
}
}
}
// Has reports whether a field is populated.
@ -248,6 +257,8 @@ func (x *fastReflection_A) Has(fd protoreflect.FieldDescriptor) bool {
return len(x.COINS) != 0
case "A.BYTES":
return len(x.BYTES) != 0
case "A.TIMESTAMP":
return x.TIMESTAMP != nil
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: A"))
@ -282,6 +293,8 @@ func (x *fastReflection_A) Clear(fd protoreflect.FieldDescriptor) {
x.COINS = nil
case "A.BYTES":
x.BYTES = nil
case "A.TIMESTAMP":
x.TIMESTAMP = nil
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: A"))
@ -328,6 +341,9 @@ func (x *fastReflection_A) Get(descriptor protoreflect.FieldDescriptor) protoref
case "A.BYTES":
value := x.BYTES
return protoreflect.ValueOfBytes(value)
case "A.TIMESTAMP":
value := x.TIMESTAMP
return protoreflect.ValueOfMessage(value.ProtoReflect())
default:
if descriptor.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: A"))
@ -368,6 +384,8 @@ func (x *fastReflection_A) Set(fd protoreflect.FieldDescriptor, value protorefle
x.COINS = *clv.list
case "A.BYTES":
x.BYTES = value.Bytes()
case "A.TIMESTAMP":
x.TIMESTAMP = value.Message().Interface().(*timestamppb.Timestamp)
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: A"))
@ -399,6 +417,11 @@ func (x *fastReflection_A) Mutable(fd protoreflect.FieldDescriptor) protoreflect
}
value := &_A_8_list{list: &x.COINS}
return protoreflect.ValueOfList(value)
case "A.TIMESTAMP":
if x.TIMESTAMP == nil {
x.TIMESTAMP = new(timestamppb.Timestamp)
}
return protoreflect.ValueOfMessage(x.TIMESTAMP.ProtoReflect())
case "A.UINT32":
panic(fmt.Errorf("field UINT32 of message A is not mutable"))
case "A.UINT64":
@ -446,6 +469,9 @@ func (x *fastReflection_A) NewField(fd protoreflect.FieldDescriptor) protoreflec
return protoreflect.ValueOfList(&_A_8_list{list: &list})
case "A.BYTES":
return protoreflect.ValueOfBytes(nil)
case "A.TIMESTAMP":
m := new(timestamppb.Timestamp)
return protoreflect.ValueOfMessage(m.ProtoReflect())
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: A"))
@ -549,6 +575,10 @@ func (x *fastReflection_A) ProtoMethods() *protoiface.Methods {
if l > 0 {
n += 1 + l + runtime.Sov(uint64(l))
}
if x.TIMESTAMP != nil {
l = options.Size(x.TIMESTAMP)
n += 1 + l + runtime.Sov(uint64(l))
}
if x.unknownFields != nil {
n += len(x.unknownFields)
}
@ -578,6 +608,20 @@ func (x *fastReflection_A) ProtoMethods() *protoiface.Methods {
i -= len(x.unknownFields)
copy(dAtA[i:], x.unknownFields)
}
if x.TIMESTAMP != nil {
encoded, err := options.Marshal(x.TIMESTAMP)
if err != nil {
return protoiface.MarshalOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Buf: input.Buf,
}, err
}
i -= len(encoded)
copy(dAtA[i:], encoded)
i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded)))
i--
dAtA[i] = 0x52
}
if len(x.BYTES) > 0 {
i -= len(x.BYTES)
copy(dAtA[i:], x.BYTES)
@ -942,6 +986,42 @@ func (x *fastReflection_A) ProtoMethods() *protoiface.Methods {
x.BYTES = []byte{}
}
iNdEx = postIndex
case 10:
if wireType != 2 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field TIMESTAMP", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
}
if iNdEx >= l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
if postIndex > l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
if x.TIMESTAMP == nil {
x.TIMESTAMP = &timestamppb.Timestamp{}
}
if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.TIMESTAMP); err != nil {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := runtime.Skip(dAtA[iNdEx:])
@ -2162,15 +2242,16 @@ type A struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UINT32 uint32 `protobuf:"varint,1,opt,name=UINT32,proto3" json:"UINT32,omitempty"`
UINT64 uint64 `protobuf:"varint,2,opt,name=UINT64,proto3" json:"UINT64,omitempty"`
INT32 int32 `protobuf:"varint,3,opt,name=INT32,proto3" json:"INT32,omitempty"`
INT64 int64 `protobuf:"varint,4,opt,name=INT64,proto3" json:"INT64,omitempty"`
SDKINT string `protobuf:"bytes,5,opt,name=SDKINT,proto3" json:"SDKINT,omitempty"`
SDKDEC string `protobuf:"bytes,6,opt,name=SDKDEC,proto3" json:"SDKDEC,omitempty"`
COIN *v1beta1.Coin `protobuf:"bytes,7,opt,name=COIN,proto3" json:"COIN,omitempty"`
COINS []*v1beta1.Coin `protobuf:"bytes,8,rep,name=COINS,proto3" json:"COINS,omitempty"`
BYTES []byte `protobuf:"bytes,9,opt,name=BYTES,proto3" json:"BYTES,omitempty"`
UINT32 uint32 `protobuf:"varint,1,opt,name=UINT32,proto3" json:"UINT32,omitempty"`
UINT64 uint64 `protobuf:"varint,2,opt,name=UINT64,proto3" json:"UINT64,omitempty"`
INT32 int32 `protobuf:"varint,3,opt,name=INT32,proto3" json:"INT32,omitempty"`
INT64 int64 `protobuf:"varint,4,opt,name=INT64,proto3" json:"INT64,omitempty"`
SDKINT string `protobuf:"bytes,5,opt,name=SDKINT,proto3" json:"SDKINT,omitempty"`
SDKDEC string `protobuf:"bytes,6,opt,name=SDKDEC,proto3" json:"SDKDEC,omitempty"`
COIN *v1beta1.Coin `protobuf:"bytes,7,opt,name=COIN,proto3" json:"COIN,omitempty"`
COINS []*v1beta1.Coin `protobuf:"bytes,8,rep,name=COINS,proto3" json:"COINS,omitempty"`
BYTES []byte `protobuf:"bytes,9,opt,name=BYTES,proto3" json:"BYTES,omitempty"`
TIMESTAMP *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=TIMESTAMP,proto3" json:"TIMESTAMP,omitempty"`
}
func (x *A) Reset() {
@ -2256,6 +2337,13 @@ func (x *A) GetBYTES() []byte {
return nil
}
func (x *A) GetTIMESTAMP() *timestamppb.Timestamp {
if x != nil {
return x.TIMESTAMP
}
return nil
}
// B contains fields that are not parseable by SIGN_MODE_TEXTUAL, some fields
// may be moved to A at some point.
type B struct {
@ -2378,57 +2466,62 @@ var File__1_proto protoreflect.FileDescriptor
var file__1_proto_rawDesc = []byte{
0x0a, 0x07, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x63, 0x6f, 0x73,
0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x62,
0x61, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x69, 0x6e,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa5, 0x02, 0x0a, 0x01, 0x41, 0x12, 0x16, 0x0a, 0x06,
0x55, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55, 0x49,
0x4e, 0x54, 0x33, 0x32, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x02,
0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x12, 0x14, 0x0a, 0x05,
0x49, 0x4e, 0x54, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x49, 0x4e, 0x54,
0x33, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28,
0x03, 0x52, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x12, 0x26, 0x0a, 0x06, 0x53, 0x44, 0x4b, 0x49,
0x4e, 0x54, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0e, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f,
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x06, 0x53, 0x44, 0x4b, 0x49, 0x4e, 0x54,
0x12, 0x26, 0x0a, 0x06, 0x53, 0x44, 0x4b, 0x44, 0x45, 0x43, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
0x42, 0x0e, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x44, 0x65, 0x63,
0x52, 0x06, 0x53, 0x44, 0x4b, 0x44, 0x45, 0x43, 0x12, 0x2d, 0x0a, 0x04, 0x43, 0x4f, 0x49, 0x4e,
0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e,
0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x69,
0x6e, 0x52, 0x04, 0x43, 0x4f, 0x49, 0x4e, 0x12, 0x2f, 0x0a, 0x05, 0x43, 0x4f, 0x49, 0x4e, 0x53,
0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e,
0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x69,
0x6e, 0x52, 0x05, 0x43, 0x4f, 0x49, 0x4e, 0x53, 0x12, 0x14, 0x0a, 0x05, 0x42, 0x59, 0x54, 0x45,
0x53, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x42, 0x59, 0x54, 0x45, 0x53, 0x22, 0xd4,
0x02, 0x0a, 0x01, 0x42, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x18, 0x01, 0x20,
0x01, 0x28, 0x05, 0x52, 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x49,
0x4e, 0x54, 0x33, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x11, 0x52, 0x06, 0x53, 0x49, 0x4e, 0x54,
0x33, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x03, 0x20, 0x01, 0x28,
0x03, 0x52, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x49, 0x4e, 0x47,
0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x12, 0x52, 0x06, 0x53, 0x49, 0x4e, 0x47, 0x36, 0x34,
0x12, 0x1a, 0x0a, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x18, 0x05, 0x20, 0x01,
0x28, 0x0f, 0x52, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x12, 0x18, 0x0a, 0x07,
0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x18, 0x06, 0x20, 0x01, 0x28, 0x07, 0x52, 0x07, 0x46,
0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x18,
0x07, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x12, 0x1a, 0x0a, 0x08,
0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x18, 0x08, 0x20, 0x01, 0x28, 0x10, 0x52, 0x08,
0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x12, 0x18, 0x0a, 0x07, 0x46, 0x49, 0x58, 0x45,
0x44, 0x36, 0x34, 0x18, 0x09, 0x20, 0x01, 0x28, 0x06, 0x52, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44,
0x36, 0x34, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x18, 0x0a, 0x20, 0x01,
0x28, 0x01, 0x52, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x12, 0x1d, 0x0a, 0x03, 0x4d, 0x41,
0x50, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x42, 0x2e, 0x4d, 0x41, 0x50, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x4d, 0x41, 0x50, 0x1a, 0x3a, 0x0a, 0x08, 0x4d, 0x41, 0x50,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x02, 0x2e, 0x42, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x1f, 0x0a, 0x0b, 0x45, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x07, 0x0a,
0x03, 0x54, 0x77, 0x6f, 0x10, 0x01, 0x42, 0x33, 0x42, 0x06, 0x31, 0x50, 0x72, 0x6f, 0x74, 0x6f,
0x50, 0x01, 0x5a, 0x27, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f,
0x2f, 0x74, 0x78, 0x2f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x61, 0x6c, 0x2f, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x63, 0x6f,
0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f,
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f,
0x62, 0x61, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x69,
0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdf, 0x02, 0x0a, 0x01, 0x41, 0x12, 0x16, 0x0a,
0x06, 0x55, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55,
0x49, 0x4e, 0x54, 0x33, 0x32, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18,
0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x12, 0x14, 0x0a,
0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x49, 0x4e,
0x54, 0x33, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01,
0x28, 0x03, 0x52, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x12, 0x26, 0x0a, 0x06, 0x53, 0x44, 0x4b,
0x49, 0x4e, 0x54, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0e, 0xd2, 0xb4, 0x2d, 0x0a, 0x63,
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x06, 0x53, 0x44, 0x4b, 0x49, 0x4e,
0x54, 0x12, 0x26, 0x0a, 0x06, 0x53, 0x44, 0x4b, 0x44, 0x45, 0x43, 0x18, 0x06, 0x20, 0x01, 0x28,
0x09, 0x42, 0x0e, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x44, 0x65,
0x63, 0x52, 0x06, 0x53, 0x44, 0x4b, 0x44, 0x45, 0x43, 0x12, 0x2d, 0x0a, 0x04, 0x43, 0x4f, 0x49,
0x4e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73,
0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f,
0x69, 0x6e, 0x52, 0x04, 0x43, 0x4f, 0x49, 0x4e, 0x12, 0x2f, 0x0a, 0x05, 0x43, 0x4f, 0x49, 0x4e,
0x53, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73,
0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f,
0x69, 0x6e, 0x52, 0x05, 0x43, 0x4f, 0x49, 0x4e, 0x53, 0x12, 0x14, 0x0a, 0x05, 0x42, 0x59, 0x54,
0x45, 0x53, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x42, 0x59, 0x54, 0x45, 0x53, 0x12,
0x38, 0x0a, 0x09, 0x54, 0x49, 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x18, 0x0a, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09,
0x54, 0x49, 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x22, 0xd4, 0x02, 0x0a, 0x01, 0x42, 0x12,
0x14, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05,
0x49, 0x4e, 0x54, 0x33, 0x32, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x18,
0x02, 0x20, 0x01, 0x28, 0x11, 0x52, 0x06, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x12, 0x14, 0x0a,
0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x49, 0x4e,
0x54, 0x36, 0x34, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x49, 0x4e, 0x47, 0x36, 0x34, 0x18, 0x04, 0x20,
0x01, 0x28, 0x12, 0x52, 0x06, 0x53, 0x49, 0x4e, 0x47, 0x36, 0x34, 0x12, 0x1a, 0x0a, 0x08, 0x53,
0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0f, 0x52, 0x08, 0x53,
0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x12, 0x18, 0x0a, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44,
0x33, 0x32, 0x18, 0x06, 0x20, 0x01, 0x28, 0x07, 0x52, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33,
0x32, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x18, 0x07, 0x20, 0x01, 0x28, 0x02,
0x52, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x12, 0x1a, 0x0a, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45,
0x44, 0x36, 0x34, 0x18, 0x08, 0x20, 0x01, 0x28, 0x10, 0x52, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45,
0x44, 0x36, 0x34, 0x12, 0x18, 0x0a, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x18, 0x09,
0x20, 0x01, 0x28, 0x06, 0x52, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x12, 0x16, 0x0a,
0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x44,
0x4f, 0x55, 0x42, 0x4c, 0x45, 0x12, 0x1d, 0x0a, 0x03, 0x4d, 0x41, 0x50, 0x18, 0x0b, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x42, 0x2e, 0x4d, 0x41, 0x50, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x03, 0x4d, 0x41, 0x50, 0x1a, 0x3a, 0x0a, 0x08, 0x4d, 0x41, 0x50, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x12, 0x18, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x02, 0x2e, 0x42, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x2a, 0x1f, 0x0a, 0x0b, 0x45, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x07, 0x0a, 0x03, 0x4f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x77, 0x6f, 0x10,
0x01, 0x42, 0x33, 0x42, 0x06, 0x31, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x63,
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x74, 0x78, 0x2f, 0x74,
0x65, 0x78, 0x74, 0x75, 0x61, 0x6c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -2446,22 +2539,24 @@ func file__1_proto_rawDescGZIP() []byte {
var file__1_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file__1_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file__1_proto_goTypes = []interface{}{
(Enumeration)(0), // 0: Enumeration
(*A)(nil), // 1: A
(*B)(nil), // 2: B
nil, // 3: B.MAPEntry
(*v1beta1.Coin)(nil), // 4: cosmos.base.v1beta1.Coin
(Enumeration)(0), // 0: Enumeration
(*A)(nil), // 1: A
(*B)(nil), // 2: B
nil, // 3: B.MAPEntry
(*v1beta1.Coin)(nil), // 4: cosmos.base.v1beta1.Coin
(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
}
var file__1_proto_depIdxs = []int32{
4, // 0: A.COIN:type_name -> cosmos.base.v1beta1.Coin
4, // 1: A.COINS:type_name -> cosmos.base.v1beta1.Coin
3, // 2: B.MAP:type_name -> B.MAPEntry
2, // 3: B.MAPEntry.value:type_name -> B
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
5, // 2: A.TIMESTAMP:type_name -> google.protobuf.Timestamp
3, // 3: B.MAP:type_name -> B.MAPEntry
2, // 4: B.MAPEntry.value:type_name -> B
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file__1_proto_init() }

View File

@ -0,0 +1,58 @@
package valuerenderer
import (
"context"
"fmt"
"io"
"time"
"google.golang.org/protobuf/reflect/protoreflect"
tspb "google.golang.org/protobuf/types/known/timestamppb"
)
type timestampValueRenderer struct{}
// NewTimestampValueRenderer returns a ValueRenderer for protocol buffer Timestamp messages.
// It renders timestamps using the RFC 3339 format, always using UTC as the timezone.
// Fractional seconds are only rendered if nonzero.
func NewTimestampValueRenderer() ValueRenderer {
return timestampValueRenderer{}
}
// Format implements the ValueRenderer interface.
func (vr timestampValueRenderer) Format(_ context.Context, v protoreflect.Value, w io.Writer) error {
// Reify the reflected message as a proto Timestamp
msg := v.Message().Interface()
timestamp, ok := msg.(*tspb.Timestamp)
if !ok {
return fmt.Errorf("expected Timestamp, got %T", msg)
}
// Convert proto timestamp to a Go Time.
t := timestamp.AsTime()
// Format the Go Time as RFC 3339.
s := t.Format(time.RFC3339Nano)
w.Write([]byte(s))
return nil
}
// Parse implements the ValueRenderer interface.
func (vr timestampValueRenderer) Parse(_ context.Context, r io.Reader) (protoreflect.Value, error) {
// Parse the RFC 3339 input as a Go Time.
bz, err := io.ReadAll(r)
if err != nil {
return protoreflect.Value{}, err
}
t, err := time.Parse(time.RFC3339Nano, string(bz))
if err != nil {
return protoreflect.Value{}, err
}
// Convert Go Time to a proto Timestamp.
timestamp := tspb.New(t)
// Reflect the proto Timestamp.
msg := timestamp.ProtoReflect()
return protoreflect.ValueOfMessage(msg), nil
}

View File

@ -0,0 +1,93 @@
package valuerenderer_test
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"testing"
"time"
"cosmossdk.io/tx/textual/valuerenderer"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
dur "google.golang.org/protobuf/types/known/durationpb"
tspb "google.golang.org/protobuf/types/known/timestamppb"
)
// timestampJsonTest is the type of test cases in the testdata file.
// If the test case has a Proto, try to Format() it. If Error is set, expect
// an error, otherwise match Text, then Parse() the text and expect it to
// match (via proto.Equals()) the original Proto. If the test case has no
// Proto, try to Parse() the Text and expect an error if Error is set.
//
// The Timestamp proto seconds field is int64, but restricted in range
// by convention and will fit within a JSON number.
type timestampJsonTest struct {
Proto *tspb.Timestamp
Error bool
Text string
}
func TestTimestampJsonTestcases(t *testing.T) {
raw, err := os.ReadFile("../internal/testdata/timestamp.json")
require.NoError(t, err)
var testcases []timestampJsonTest
err = json.Unmarshal(raw, &testcases)
require.NoError(t, err)
for i, tc := range testcases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
rend := valuerenderer.NewTimestampValueRenderer()
if tc.Proto != nil {
wr := new(strings.Builder)
err = rend.Format(context.Background(), protoreflect.ValueOf(tc.Proto.ProtoReflect()), wr)
if tc.Error {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.Text, wr.String())
}
rd := strings.NewReader(tc.Text)
val, err := rend.Parse(context.Background(), rd)
if tc.Error {
require.Error(t, err)
return
}
require.NoError(t, err)
msg := val.Message().Interface()
require.IsType(t, &tspb.Timestamp{}, msg)
timestamp := msg.(*tspb.Timestamp)
require.True(t, proto.Equal(timestamp, tc.Proto))
})
}
}
func TestTimestampBadFormat(t *testing.T) {
rend := valuerenderer.NewTimestampValueRenderer()
wr := new(strings.Builder)
err := rend.Format(context.Background(), protoreflect.ValueOf(dur.New(time.Hour).ProtoReflect()), wr)
require.Error(t, err)
}
type badReader struct{}
var _ io.Reader = badReader{}
func (br badReader) Read(p []byte) (int, error) {
return 0, fmt.Errorf("reader error")
}
func TestTimestampBadParse_reader(t *testing.T) {
rend := valuerenderer.NewTimestampValueRenderer()
_, err := rend.Parse(context.Background(), badReader{})
require.ErrorContains(t, err, "reader error")
}

View File

@ -9,18 +9,25 @@ import (
cosmos_proto "github.com/cosmos/cosmos-proto"
)
// Textual holds the configuration for dispatching
// to specific value renderers for SIGN_MODE_TEXTUAL.
type Textual struct {
scalars map[string]ValueRenderer
scalars map[string]ValueRenderer
messages map[protoreflect.FullName]ValueRenderer
}
// NewTextual returns a new Textual which provides
// value renderers.
func NewTextual() Textual {
return Textual{}
t := Textual{}
t.init()
return t
}
// GetValueRenderer returns the value renderer for the given FieldDescriptor.
func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRenderer, error) {
switch {
// Scalars, such as sdk.Int and sdk.Dec.
// Scalars, such as sdk.Int and sdk.Dec encoded as strings.
case fd.Kind() == protoreflect.StringKind && proto.GetExtension(fd.Options(), cosmos_proto.E_Scalar) != "":
{
scalar, ok := proto.GetExtension(fd.Options(), cosmos_proto.E_Scalar).(string)
@ -28,10 +35,6 @@ func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRendere
return nil, fmt.Errorf("got extension option %s of type %T", scalar, scalar)
}
if r.scalars == nil {
r.init()
}
vr := r.scalars[scalar]
if vr == nil {
return nil, fmt.Errorf("got empty value renderer for scalar %s", scalar)
@ -51,6 +54,17 @@ func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRendere
return intValueRenderer{}, nil
}
case fd.Kind() == protoreflect.MessageKind:
md := fd.Message()
fullName := md.FullName()
vr, found := r.messages[fullName]
if found {
return vr, nil
}
// TODO default message renderer
return nil, fmt.Errorf("no value renderer for message %s", fullName)
default:
return nil, fmt.Errorf("value renderers cannot format value of type %s", fd.Kind())
}
@ -62,6 +76,10 @@ func (r *Textual) init() {
r.scalars["cosmos.Int"] = intValueRenderer{}
r.scalars["cosmos.Dec"] = decValueRenderer{}
}
if r.messages == nil {
r.messages = map[protoreflect.FullName]ValueRenderer{}
r.messages["google.protobuf.Timestamp"] = NewTimestampValueRenderer()
}
}
// DefineScalar adds a value renderer to the given Cosmos scalar.

View File

@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
tspb "google.golang.org/protobuf/types/known/timestamppb"
"cosmossdk.io/math"
"cosmossdk.io/tx/textual/internal/testpb"
@ -116,6 +117,14 @@ func TestGetADR050ValueRenderer(t *testing.T) {
}
}
func TestTimestampDispatch(t *testing.T) {
a := (&testpb.A{}).ProtoReflect().Descriptor().Fields()
textual := valuerenderer.NewTextual()
rend, err := textual.GetValueRenderer(a.ByName(protoreflect.Name("TIMESTAMP")))
require.NoError(t, err)
require.IsType(t, valuerenderer.NewTimestampValueRenderer(), rend)
}
// valueRendererOf is like GetADR050ValueRenderer, but taking a Go type
// as input instead of a protoreflect.FieldDescriptor.
func valueRendererOf(v interface{}) (valuerenderer.ValueRenderer, error) {
@ -138,6 +147,8 @@ func valueRendererOf(v interface{}) (valuerenderer.ValueRenderer, error) {
return textual.GetValueRenderer(a.ByName(protoreflect.Name("SDKINT")))
case math.LegacyDec:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("SDKDEC")))
case tspb.Timestamp:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("TIMESTAMP")))
// Invalid types for SIGN_MODE_TEXTUAL
case float32: