From 87b302da033678d993f81042b9bf214f2a57dc78 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 7 Nov 2024 15:21:58 +0800 Subject: [PATCH] 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) + }) +}