Skip to content

Commit

Permalink
feat(values): adds support for time.Time as value
Browse files Browse the repository at this point in the history
  • Loading branch information
MStreet3 committed Sep 20, 2024
1 parent 34e8551 commit 0007a40
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 129 deletions.
13 changes: 13 additions & 0 deletions pkg/values/pb/values.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package pb

import (
"time"

"github.com/shopspring/decimal"
"google.golang.org/protobuf/types/known/timestamppb"
)

func NewBoolValue(b bool) *Value {
Expand Down Expand Up @@ -80,3 +83,13 @@ func NewBigIntValue(sign int, bib []byte) *Value {
},
}
}

func NewTime(t time.Time) *Value {
return &Value{
Value: &Value_TimeValue{
TimeValue: &Time{
Time: timestamppb.New(t),
},
},
}
}
329 changes: 208 additions & 121 deletions pkg/values/pb/values.pb.go

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions pkg/values/pb/values.proto
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
syntax = "proto3";

import "google/protobuf/timestamp.proto";

option go_package = "github.com/smartcontractkit/chainlink-common/pkg/values/pb";

package values;
Expand All @@ -15,23 +17,22 @@ message Value {
Decimal decimal_value = 6;
int64 int64_value = 7;
BigInt bigint_value = 9;
Time time_value = 10;
}
}

message BigInt {
bytes abs_val = 1;
int64 sign = 2;
bytes abs_val = 1;
int64 sign = 2;
}

message Map {
map<string, Value> fields = 1;
}
message Map { map<string, Value> fields = 1; }

message List {
repeated Value fields = 2;
}
message List { repeated Value fields = 2; }

message Decimal {
BigInt coefficient = 1;
int32 exponent = 2;
}

message Time { google.protobuf.Timestamp time = 1; }
41 changes: 41 additions & 0 deletions pkg/values/time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package values

import (
"errors"
"time"

"github.com/smartcontractkit/chainlink-common/pkg/values/pb"
)

type Time struct {
Underlying time.Time
}

func NewTime(t time.Time) *Time {
return &Time{Underlying: t}
}

func (t *Time) UnwrapTo(to any) error {
if t == nil {
return errors.New("could not unwrap nil values.Time")
}

return unwrapTo(t.Underlying, to)
}

func (t *Time) Unwrap() (any, error) {
tt := new(time.Time)
return *tt, t.UnwrapTo(tt)
}

func (t *Time) copy() Value {
if t == nil {
return nil
}

return NewTime(t.Underlying)
}

func (t *Time) proto() *pb.Value {
return pb.NewTime(t.Underlying)
}
88 changes: 88 additions & 0 deletions pkg/values/time_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package values

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func Test_TimeUnwrapTo(t *testing.T) {
expected, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
assert.NoError(t, err)

// Unwraps to a time.Time pointer
v := NewTime(expected)
got := new(time.Time)
err = v.UnwrapTo(got)
assert.NoError(t, err)
assert.Equal(t, expected, *got)

// Fails to unwrap to nil time.Time pointer
gotTime := (*time.Time)(nil)
err = v.UnwrapTo(gotTime)
assert.Error(t, err)
assert.ErrorContains(t, err, "cannot unwrap to nil pointer")

// Unwraps to an any pointer
var varAny any
err = v.UnwrapTo(&varAny)
assert.NoError(t, err)
assert.Equal(t, expected, varAny)

// Fails to unwrap to a string pointer
var varStr string
err = v.UnwrapTo(&varStr)
assert.Error(t, err)
assert.ErrorContains(t, err, "cannot unwrap to value of type: *string")

// Fails to unwrap nil value of Time
nilVal := (*Time)(nil)
_, err = nilVal.Unwrap()
assert.Error(t, err)
assert.ErrorContains(t, err, "could not unwrap nil")

// Unwraps zero value of Time
zeroTime := &Time{}
unwrapped, err := zeroTime.Unwrap()
assert.NoError(t, err)
assert.Equal(t, time.Time{}, unwrapped)

// Unwraps an alias
type aliasTime time.Time
alias := aliasTime(time.Time{})
err = v.UnwrapTo(&alias)
assert.NoError(t, err)
}

// Test_Time tests that Time values can be converted to and from protobuf representations.
func Test_Time(t *testing.T) {
testCases := []struct {
name string
t time.Time
}{
{
name: "zero",
t: time.Time{},
},
{
name: "some time",
t: func() time.Time {
someTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
assert.NoError(t, err)
return someTime
}(),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
v := NewTime(tc.t)

vp := Proto(v)
got, err := FromProto(vp)
assert.NoError(t, err)
assert.Equal(t, tc.t, got.(*Time).Underlying)
})
}
}
2 changes: 2 additions & 0 deletions pkg/values/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ func FromProto(val *pb.Value) (Value, error) {
return FromMapValueProto(val.GetMapValue())
case *pb.Value_BigintValue:
return fromBigIntValueProto(val.GetBigintValue()), nil
case *pb.Value_TimeValue:
return NewTime(val.GetTimeValue().Time.AsTime()), nil
}

return nil, fmt.Errorf("unsupported type %T: %+v", val, val)
Expand Down
16 changes: 16 additions & 0 deletions pkg/values/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"math"
"math/big"
"testing"
"time"

"github.com/go-viper/mapstructure/v2"
"github.com/shopspring/decimal"
Expand Down Expand Up @@ -100,6 +101,14 @@ func Test_Value(t *testing.T) {
return b, bv, nil
},
},
{
name: "time",
newValue: func() (any, Value, error) {
t, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
tv := NewTime(t)
return t, tv, err
},
},
{
name: "recursive map",
newValue: func() (any, Value, error) {
Expand Down Expand Up @@ -343,6 +352,9 @@ func Test_Copy(t *testing.T) {
{
value: mp,
},
{
value: NewTime(time.Time{}),
},
{
value: (*String)(nil),
isNil: true,
Expand Down Expand Up @@ -371,6 +383,10 @@ func Test_Copy(t *testing.T) {
value: (*Map)(nil),
isNil: true,
},
{
value: (*Time)(nil),
isNil: true,
},
}

for _, tc := range tcs {
Expand Down

0 comments on commit 0007a40

Please sign in to comment.