From 87b302da033678d993f81042b9bf214f2a57dc78 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 7 Nov 2024 15:21:58 +0800 Subject: [PATCH 1/4] dynamic: add dynamic compare --- pkg/dynamic/compare.go | 133 ++++++++++++++++++++++++++++++++++++ pkg/dynamic/compare_test.go | 48 +++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 pkg/dynamic/compare.go create mode 100644 pkg/dynamic/compare_test.go diff --git a/pkg/dynamic/compare.go b/pkg/dynamic/compare.go new file mode 100644 index 000000000..bb89c3ac4 --- /dev/null +++ b/pkg/dynamic/compare.go @@ -0,0 +1,133 @@ +package dynamic + +import ( + "fmt" + "reflect" + "strconv" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +type Diff struct { + Field string `json:"field"` + Before string `json:"before"` + After string `json:"after"` +} + +func isSimpleType(kind reflect.Kind) bool { + switch kind { + case reflect.Bool, reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint64, reflect.String, reflect.Float64: + return true + default: + return false + } +} + +func compareSimpleValue(a, b reflect.Value) bool { + if a.Kind() != b.Kind() { + return false + } + + switch a.Kind() { + case reflect.Bool: + if a.Bool() == b.Bool() { + return true + } + + case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint64: + if a.Int() == b.Int() { + return true + } + + case reflect.String: + if a.String() == b.String() { + return true + } + + case reflect.Float64: + if a.Float() == b.Float() { + return true + } + + case reflect.Slice: + // TODO: compare slice + + default: + // unhandled case + + } + + return false +} + +// a (after) +// b (before) +func Compare(a, b interface{}) ([]Diff, error) { + var diffs []Diff + + ra := reflect.ValueOf(a) + raType := ra.Type() + raKind := ra.Kind() + + rb := reflect.ValueOf(b) + rbType := rb.Type() + rbKind := rb.Kind() // bool, int, slice, string, struct + + if raType != rbType { + return nil, fmt.Errorf("type mismatch: %s != %s", raType, rbType) + } + + if raKind != rbKind { + return nil, fmt.Errorf("kind mismatch: %s != %s", raKind, rbKind) + } + + if isSimpleType(raKind) { + if compareSimpleValue(ra, rb) { + // no changes + return nil, nil + } else { + return []Diff{ + { + Field: "", + Before: convertToStr(rb), + After: convertToStr(ra), + }, + }, nil + } + } + + return diffs, nil +} + +func convertToStr(val reflect.Value) string { + if val.Type() == reflect.TypeOf(fixedpoint.Zero) { + fp := val.Interface().(fixedpoint.Value) + return fp.String() + } + + switch val.Kind() { + case reflect.Float32, reflect.Float64: + return strconv.FormatFloat(val.Float(), 'f', -1, 64) + + case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64: + return strconv.FormatInt(val.Int(), 10) + + case reflect.Uint, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(val.Uint(), 10) + + case reflect.Bool: + if val.Bool() { + return "true" + } else { + return "false" + } + default: + strType := reflect.TypeOf("") + if val.CanConvert(strType) { + strVal := val.Convert(strType) + return strVal.String() + } + + return "{unable to convert " + val.Kind().String() + "}" + } +} diff --git a/pkg/dynamic/compare_test.go b/pkg/dynamic/compare_test.go new file mode 100644 index 000000000..cf4ca707e --- /dev/null +++ b/pkg/dynamic/compare_test.go @@ -0,0 +1,48 @@ +package dynamic + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +func Test_convertToStr(t *testing.T) { + t.Run("str-str", func(t *testing.T) { + out := convertToStr(reflect.ValueOf("a")) + assert.Equal(t, "a", out) + }) + + t.Run("bool-str", func(t *testing.T) { + out := convertToStr(reflect.ValueOf(false)) + assert.Equal(t, "false", out) + + out = convertToStr(reflect.ValueOf(true)) + assert.Equal(t, "true", out) + }) + + t.Run("float-str", func(t *testing.T) { + out := convertToStr(reflect.ValueOf(0.444)) + assert.Equal(t, "0.444", out) + }) + + t.Run("int-str", func(t *testing.T) { + a := int(123) + out := convertToStr(reflect.ValueOf(a)) + assert.Equal(t, "123", out) + }) + + t.Run("uint-str", func(t *testing.T) { + a := uint(123) + out := convertToStr(reflect.ValueOf(a)) + assert.Equal(t, "123", out) + }) + + t.Run("fixedpoint-str", func(t *testing.T) { + a := fixedpoint.NewFromInt(100) + out := convertToStr(reflect.ValueOf(a)) + assert.Equal(t, "100", out) + }) +} From 7b94c785ba16be9c15d5b6277131f1561250f603 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 7 Nov 2024 16:31:02 +0800 Subject: [PATCH 2/4] dynamic: compareStruct and compareSimpleValue --- pkg/dynamic/compare.go | 178 ++++++++++++++++++++-------- pkg/dynamic/compare_test.go | 84 +++++++++++++ pkg/notifier/slacknotifier/slack.go | 32 ++++- 3 files changed, 238 insertions(+), 56 deletions(-) diff --git a/pkg/dynamic/compare.go b/pkg/dynamic/compare.go index bb89c3ac4..4ff6890a7 100644 --- a/pkg/dynamic/compare.go +++ b/pkg/dynamic/compare.go @@ -14,62 +14,24 @@ type Diff struct { After string `json:"after"` } -func isSimpleType(kind reflect.Kind) bool { - switch kind { - case reflect.Bool, reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint64, reflect.String, reflect.Float64: - return true - default: - return false - } -} - -func compareSimpleValue(a, b reflect.Value) bool { - if a.Kind() != b.Kind() { - return false - } - - switch a.Kind() { - case reflect.Bool: - if a.Bool() == b.Bool() { - return true - } - - case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint64: - if a.Int() == b.Int() { - return true - } - - case reflect.String: - if a.String() == b.String() { - return true - } - - case reflect.Float64: - if a.Float() == b.Float() { - return true - } - - case reflect.Slice: - // TODO: compare slice - - default: - // unhandled case - - } - - return false -} - // a (after) // b (before) func Compare(a, b interface{}) ([]Diff, error) { var diffs []Diff ra := reflect.ValueOf(a) + if ra.Kind() == reflect.Ptr { + ra = ra.Elem() + } + raType := ra.Type() raKind := ra.Kind() rb := reflect.ValueOf(b) + if rb.Kind() == reflect.Ptr { + rb = rb.Elem() + } + rbType := rb.Type() rbKind := rb.Kind() // bool, int, slice, string, struct @@ -96,15 +58,133 @@ func Compare(a, b interface{}) ([]Diff, error) { } } + if raKind == reflect.Struct { + + } + return diffs, nil } +func compareStruct(a, b reflect.Value) ([]Diff, error) { + if a.Kind() == reflect.Ptr { + a = a.Elem() + } + + if b.Kind() == reflect.Ptr { + b = b.Elem() + } + + if a.Kind() != reflect.Struct { + return nil, fmt.Errorf("value is not a struct") + } + + if b.Kind() != reflect.Struct { + return nil, fmt.Errorf("value is not a struct") + } + + if a.Type() != b.Type() { + return nil, fmt.Errorf("type is not the same") + } + + var diffs []Diff + + numFields := a.NumField() + for i := 0; i < numFields; i++ { + fieldValueA := a.Field(i) + fieldValueB := b.Field(i) + + fieldName := a.Type().Field(i).Name + + if isSimpleType(fieldValueA.Kind()) { + if compareSimpleValue(fieldValueA, fieldValueB) { + continue + } else { + diffs = append(diffs, Diff{ + Field: fieldName, + Before: convertToStr(fieldValueB), + After: convertToStr(fieldValueA), + }) + } + } else if fieldValueA.Kind() == reflect.Struct && fieldValueB.Kind() == reflect.Struct { + subDiffs, err := compareStruct(fieldValueA, fieldValueB) + if err != nil { + return diffs, err + } + + for _, subDiff := range subDiffs { + diffs = append(diffs, Diff{ + Field: fieldName + "." + subDiff.Field, + Before: subDiff.Before, + After: subDiff.After, + }) + } + } + } + + return diffs, nil +} + +func isSimpleType(kind reflect.Kind) bool { + switch kind { + case reflect.Bool, reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint64, reflect.String, reflect.Float64: + return true + default: + return false + } +} + +func compareSimpleValue(a, b reflect.Value) bool { + if a.Kind() != b.Kind() { + return false + } + + switch a.Kind() { + + case reflect.Bool: + if a.Bool() == b.Bool() { + return true + } + + case reflect.Uint, reflect.Uint32, reflect.Uint64: + if a.Uint() == b.Uint() { + return true + } + case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64: + if a.Int() == b.Int() { + return true + } + + case reflect.String: + if a.String() == b.String() { + return true + } + + case reflect.Float64: + if a.Float() == b.Float() { + return true + } + + case reflect.Slice: + // TODO: compare slice + + default: + // unhandled case + + } + + return false +} + func convertToStr(val reflect.Value) string { if val.Type() == reflect.TypeOf(fixedpoint.Zero) { fp := val.Interface().(fixedpoint.Value) return fp.String() } + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + switch val.Kind() { case reflect.Float32, reflect.Float64: return strconv.FormatFloat(val.Float(), 'f', -1, 64) @@ -116,11 +196,7 @@ func convertToStr(val reflect.Value) string { return strconv.FormatUint(val.Uint(), 10) case reflect.Bool: - if val.Bool() { - return "true" - } else { - return "false" - } + return strconv.FormatBool(val.Bool()) default: strType := reflect.TypeOf("") if val.CanConvert(strType) { diff --git a/pkg/dynamic/compare_test.go b/pkg/dynamic/compare_test.go index cf4ca707e..b5da05215 100644 --- a/pkg/dynamic/compare_test.go +++ b/pkg/dynamic/compare_test.go @@ -1,12 +1,14 @@ package dynamic import ( + "fmt" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" ) func Test_convertToStr(t *testing.T) { @@ -40,9 +42,91 @@ func Test_convertToStr(t *testing.T) { assert.Equal(t, "123", out) }) + t.Run("int-ptr-str", func(t *testing.T) { + a := int(123) + out := convertToStr(reflect.ValueOf(&a)) + assert.Equal(t, "123", out) + }) + t.Run("fixedpoint-str", func(t *testing.T) { a := fixedpoint.NewFromInt(100) out := convertToStr(reflect.ValueOf(a)) assert.Equal(t, "100", out) }) } + +func Test_compareStruct(t *testing.T) { + tests := []struct { + name string + a, b reflect.Value + want []Diff + wantErr assert.ErrorAssertionFunc + }{ + { + name: "order ptrs", + wantErr: assert.NoError, + a: reflect.ValueOf(&types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: "BTCUSDT", + Quantity: fixedpoint.NewFromFloat(100.0), + }, + ExecutedQuantity: fixedpoint.NewFromFloat(50.0), + }), + b: reflect.ValueOf(&types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: "BTCUSDT", + Quantity: fixedpoint.NewFromFloat(100.0), + }, + ExecutedQuantity: fixedpoint.NewFromFloat(20.0), + }), + want: []Diff{ + { + Field: "ExecutedQuantity", + Before: "20", + After: "50", + }, + }, + }, + { + name: "order ptr and value", + wantErr: assert.NoError, + a: reflect.ValueOf(types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: "BTCUSDT", + Quantity: fixedpoint.NewFromFloat(100.0), + }, + Status: types.OrderStatusFilled, + ExecutedQuantity: fixedpoint.NewFromFloat(100.0), + }), + b: reflect.ValueOf(&types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: "BTCUSDT", + Quantity: fixedpoint.NewFromFloat(100.0), + }, + ExecutedQuantity: fixedpoint.NewFromFloat(50.0), + Status: types.OrderStatusPartiallyFilled, + }), + want: []Diff{ + { + Field: "Status", + Before: "PARTIALLY_FILLED", + After: "FILLED", + }, + { + Field: "ExecutedQuantity", + Before: "50", + After: "100", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := compareStruct(tt.a, tt.b) + if !tt.wantErr(t, err, fmt.Sprintf("compareStruct(%v, %v)", tt.a, tt.b)) { + return + } + assert.Equalf(t, tt.want, got, "compareStruct(%v, %v)", tt.a, tt.b) + }) + } +} diff --git a/pkg/notifier/slacknotifier/slack.go b/pkg/notifier/slacknotifier/slack.go index 09a3f84e9..db127c8f7 100644 --- a/pkg/notifier/slacknotifier/slack.go +++ b/pkg/notifier/slacknotifier/slack.go @@ -71,7 +71,17 @@ func (n *Notifier) worker() { } } -func (n *Notifier) PostLiveNote(obj livenote.Object) error { +type LiveNoteOption interface{} + +type Mention struct { + User string +} + +type Comment struct { + Text string +} + +func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...LiveNoteOption) error { note := n.liveNotePool.Update(obj) ctx := context.Background() @@ -87,18 +97,30 @@ func (n *Notifier) PostLiveNote(obj livenote.Object) error { return fmt.Errorf("livenote object does not support types.SlackAttachmentCreator interface") } - opts := slack.MsgOptionAttachments(attachment) + var slackOpts []slack.MsgOption + slackOpts = append(slackOpts, slack.MsgOptionAttachments(attachment)) + + var mentions []*Mention + var comments []*Comment + for _, opt := range opts { + switch val := opt.(type) { + case *Mention: + mentions = append(mentions, val) + case *Comment: + comments = append(comments, val) + + } + } if note.MessageID != "" { // UpdateMessageContext returns channel, timestamp, text, err - _, _, _, err := n.client.UpdateMessageContext(ctx, channel, note.MessageID, opts) + _, _, _, err := n.client.UpdateMessageContext(ctx, channel, note.MessageID, slackOpts...) if err != nil { return err } } else { - - respCh, respTs, err := n.client.PostMessageContext(ctx, channel, opts) + respCh, respTs, err := n.client.PostMessageContext(ctx, channel, slackOpts...) if err != nil { log.WithError(err). WithField("channel", n.channel). From bdc89ca579432ce7df0f62edc6075168d42089d6 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 7 Nov 2024 16:44:55 +0800 Subject: [PATCH 3/4] dynamic: implement Compare() and add tests to Compare() --- pkg/bbgo/livenote.go | 9 ++-- pkg/dynamic/compare.go | 17 ++++--- pkg/dynamic/compare_test.go | 75 +++++++++++++++++++++++++++++ pkg/livenote/options.go | 12 +++++ pkg/notifier/slacknotifier/slack.go | 24 +++------ 5 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 pkg/livenote/options.go diff --git a/pkg/bbgo/livenote.go b/pkg/bbgo/livenote.go index 20f92e367..e51f41668 100644 --- a/pkg/bbgo/livenote.go +++ b/pkg/bbgo/livenote.go @@ -6,21 +6,22 @@ import ( "github.com/c9s/bbgo/pkg/livenote" ) -// PostLiveNote posts a live note to slack or other services +// PostLiveNote a global function helper for strategies to call. +// This function posts a live note to slack or other services // The MessageID will be set after the message is posted if it's not set. -func PostLiveNote(obj livenote.Object) { +func PostLiveNote(obj livenote.Object, opts ...livenote.Option) { if len(Notification.liveNotePosters) == 0 { logrus.Warn("no live note poster is registered") return } for _, poster := range Notification.liveNotePosters { - if err := poster.PostLiveNote(obj); err != nil { + if err := poster.PostLiveNote(obj, opts...); err != nil { logrus.WithError(err).Errorf("unable to post live note: %+v", obj) } } } type LiveNotePoster interface { - PostLiveNote(note livenote.Object) error + PostLiveNote(note livenote.Object, opts ...livenote.Option) error } diff --git a/pkg/dynamic/compare.go b/pkg/dynamic/compare.go index 4ff6890a7..203f98ff8 100644 --- a/pkg/dynamic/compare.go +++ b/pkg/dynamic/compare.go @@ -17,8 +17,6 @@ type Diff struct { // a (after) // b (before) func Compare(a, b interface{}) ([]Diff, error) { - var diffs []Diff - ra := reflect.ValueOf(a) if ra.Kind() == reflect.Ptr { ra = ra.Elem() @@ -56,13 +54,11 @@ func Compare(a, b interface{}) ([]Diff, error) { }, }, nil } + } else if raKind == reflect.Struct { + return compareStruct(ra, rb) } - if raKind == reflect.Struct { - - } - - return diffs, nil + return nil, nil } func compareStruct(a, b reflect.Value) ([]Diff, error) { @@ -93,7 +89,12 @@ func compareStruct(a, b reflect.Value) ([]Diff, error) { fieldValueA := a.Field(i) fieldValueB := b.Field(i) - fieldName := a.Type().Field(i).Name + fieldA := a.Type().Field(i) + fieldName := fieldA.Name + + if !fieldA.IsExported() { + continue + } if isSimpleType(fieldValueA.Kind()) { if compareSimpleValue(fieldValueA, fieldValueB) { diff --git a/pkg/dynamic/compare_test.go b/pkg/dynamic/compare_test.go index b5da05215..36200f545 100644 --- a/pkg/dynamic/compare_test.go +++ b/pkg/dynamic/compare_test.go @@ -55,6 +55,81 @@ func Test_convertToStr(t *testing.T) { }) } +func Test_Compare(t *testing.T) { + tests := []struct { + name string + a, b interface{} + want []Diff + wantErr assert.ErrorAssertionFunc + }{ + { + name: "order", + wantErr: assert.NoError, + a: &types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: "BTCUSDT", + Quantity: fixedpoint.NewFromFloat(100.0), + }, + Status: types.OrderStatusFilled, + ExecutedQuantity: fixedpoint.NewFromFloat(100.0), + }, + b: &types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: "BTCUSDT", + Quantity: fixedpoint.NewFromFloat(100.0), + }, + ExecutedQuantity: fixedpoint.NewFromFloat(50.0), + Status: types.OrderStatusPartiallyFilled, + }, + want: []Diff{ + { + Field: "Status", + Before: "PARTIALLY_FILLED", + After: "FILLED", + }, + { + Field: "ExecutedQuantity", + Before: "50", + After: "100", + }, + }, + }, + { + name: "deposit and order", + wantErr: assert.NoError, + a: &types.Deposit{ + Address: "0x6666", + TransactionID: "0x3333", + Status: types.DepositPending, + Confirmation: "10/15", + }, + b: &types.Deposit{ + Address: "0x6666", + TransactionID: "0x3333", + Status: types.DepositPending, + Confirmation: "1/15", + }, + want: []Diff{ + { + Field: "Confirmation", + Before: "1/15", + After: "10/15", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Compare(tt.a, tt.b) + if !tt.wantErr(t, err, fmt.Sprintf("Compare(%v, %v)", tt.a, tt.b)) { + return + } + + assert.Equalf(t, tt.want, got, "Compare(%v, %v)", tt.a, tt.b) + }) + } +} + func Test_compareStruct(t *testing.T) { tests := []struct { name string diff --git a/pkg/livenote/options.go b/pkg/livenote/options.go new file mode 100644 index 000000000..216dddec0 --- /dev/null +++ b/pkg/livenote/options.go @@ -0,0 +1,12 @@ +package livenote + +type Option interface{} + +type Mention struct { + User string +} + +type Comment struct { + Text string + Users []string +} diff --git a/pkg/notifier/slacknotifier/slack.go b/pkg/notifier/slacknotifier/slack.go index db127c8f7..238a6bca7 100644 --- a/pkg/notifier/slacknotifier/slack.go +++ b/pkg/notifier/slacknotifier/slack.go @@ -71,17 +71,7 @@ func (n *Notifier) worker() { } } -type LiveNoteOption interface{} - -type Mention struct { - User string -} - -type Comment struct { - Text string -} - -func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...LiveNoteOption) error { +func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) error { note := n.liveNotePool.Update(obj) ctx := context.Background() @@ -100,15 +90,17 @@ func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...LiveNoteOption) err var slackOpts []slack.MsgOption slackOpts = append(slackOpts, slack.MsgOptionAttachments(attachment)) - var mentions []*Mention - var comments []*Comment + var userIds []string + var mentions []*livenote.Mention + var comments []*livenote.Comment for _, opt := range opts { switch val := opt.(type) { - case *Mention: + case *livenote.Mention: mentions = append(mentions, val) - case *Comment: + userIds = append(userIds, val.User) + case *livenote.Comment: comments = append(comments, val) - + userIds = append(userIds, val.Users...) } } From eb91f9d77ffa3ee9ec3727922eeea2620b97c5c0 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 7 Nov 2024 18:05:19 +0800 Subject: [PATCH 4/4] dynamic: fix compare for fixedpoint and time.Time --- pkg/dynamic/compare.go | 59 ++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/pkg/dynamic/compare.go b/pkg/dynamic/compare.go index 203f98ff8..a99e18201 100644 --- a/pkg/dynamic/compare.go +++ b/pkg/dynamic/compare.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "strconv" + "time" "github.com/c9s/bbgo/pkg/fixedpoint" ) @@ -41,7 +42,7 @@ func Compare(a, b interface{}) ([]Diff, error) { return nil, fmt.Errorf("kind mismatch: %s != %s", raKind, rbKind) } - if isSimpleType(raKind) { + if isSimpleType(ra) { if compareSimpleValue(ra, rb) { // no changes return nil, nil @@ -62,13 +63,8 @@ func Compare(a, b interface{}) ([]Diff, error) { } func compareStruct(a, b reflect.Value) ([]Diff, error) { - if a.Kind() == reflect.Ptr { - a = a.Elem() - } - - if b.Kind() == reflect.Ptr { - b = b.Elem() - } + a = reflect.Indirect(a) + b = reflect.Indirect(b) if a.Kind() != reflect.Struct { return nil, fmt.Errorf("value is not a struct") @@ -96,7 +92,7 @@ func compareStruct(a, b reflect.Value) ([]Diff, error) { continue } - if isSimpleType(fieldValueA.Kind()) { + if isSimpleType(fieldValueA) { if compareSimpleValue(fieldValueA, fieldValueB) { continue } else { @@ -125,7 +121,20 @@ func compareStruct(a, b reflect.Value) ([]Diff, error) { return diffs, nil } -func isSimpleType(kind reflect.Kind) bool { +func isSimpleType(a reflect.Value) bool { + a = reflect.Indirect(a) + aInf := a.Interface() + + switch aInf.(type) { + case time.Time: + return true + + case fixedpoint.Value: + return true + + } + + kind := a.Kind() switch kind { case reflect.Bool, reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint64, reflect.String, reflect.Float64: return true @@ -169,21 +178,37 @@ func compareSimpleValue(a, b reflect.Value) bool { // TODO: compare slice default: - // unhandled case + ainf := a.Interface() + binf := b.Interface() + switch aa := ainf.(type) { + case fixedpoint.Value: + if bb, ok := binf.(fixedpoint.Value); ok { + return bb.Compare(aa) == 0 + } + case time.Time: + if bb, ok := binf.(time.Time); ok { + return bb.Compare(aa) == 0 + } + } + + // other unhandled cases } return false } func convertToStr(val reflect.Value) string { - if val.Type() == reflect.TypeOf(fixedpoint.Zero) { - fp := val.Interface().(fixedpoint.Value) - return fp.String() - } + val = reflect.Indirect(val) - if val.Kind() == reflect.Ptr { - val = val.Elem() + if val.Type() == reflect.TypeOf(fixedpoint.Zero) { + inf := val.Interface() + switch aa := inf.(type) { + case fixedpoint.Value: + return aa.String() + case time.Time: + return aa.String() + } } switch val.Kind() {