From ef173c2a0e08271605d1fe764a0437bf9430af7a Mon Sep 17 00:00:00 2001 From: siyual-park Date: Sat, 16 Nov 2024 11:27:11 +0900 Subject: [PATCH] feat: support duration and time string format --- cmd/pkg/cli/debug_test.go | 2 +- pkg/types/encoding.go | 4 ++ pkg/types/encoding_test.go | 20 +++++++- pkg/types/time.go | 99 ++++++++++++++++++++++++++++++++++++++ pkg/types/time_test.go | 60 +++++++++++++++++++++++ 5 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 pkg/types/time.go create mode 100644 pkg/types/time_test.go diff --git a/cmd/pkg/cli/debug_test.go b/cmd/pkg/cli/debug_test.go index 3c6f1cca..338deb27 100644 --- a/cmd/pkg/cli/debug_test.go +++ b/cmd/pkg/cli/debug_test.go @@ -637,7 +637,7 @@ func TestDebugModel_Update(t *testing.T) { m.Update(tea.KeyMsg{Type: tea.KeyEnter}) data, _ := json.Marshal(types.InterfaceOf(payload)) - assert.Contains(t, m.View(), data) + assert.Contains(t, m.View(), string(data)) d.RemoveBreakpoint(d.Breakpoint()) }) diff --git a/pkg/types/encoding.go b/pkg/types/encoding.go index 00ac8963..aabcc440 100644 --- a/pkg/types/encoding.go +++ b/pkg/types/encoding.go @@ -36,6 +36,8 @@ func init() { Encoder.Add(newBinaryEncoder()) Encoder.Add(newStringEncoder()) Encoder.Add(newErrorEncoder()) + Encoder.Add(newTimeEncoder()) + Encoder.Add(newDurationEncoder()) Encoder.Add(newExpandedEncoder()) Encoder.Add(newShortcutEncoder()) @@ -49,6 +51,8 @@ func init() { Decoder.Add(newBinaryDecoder()) Decoder.Add(newStringDecoder()) Decoder.Add(newErrorDecoder()) + Decoder.Add(newTimeDecoder()) + Decoder.Add(newDurationDecoder()) Decoder.Add(newExpandedDecoder()) Decoder.Add(newShortcutDecoder()) } diff --git a/pkg/types/encoding_test.go b/pkg/types/encoding_test.go index 88816d39..9585d5c4 100644 --- a/pkg/types/encoding_test.go +++ b/pkg/types/encoding_test.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "testing" + "time" "github.com/samber/lo" "github.com/siyul-park/uniflow/pkg/encoding" @@ -30,7 +31,7 @@ func TestMarshal(t *testing.T) { expect: True, }, { - when: int(0), + when: 0, expect: NewInt(0), }, { @@ -45,6 +46,14 @@ func TestMarshal(t *testing.T) { when: "a", expect: NewString("a"), }, + { + when: time.Date(2024, time.November, 16, 12, 0, 0, 0, time.UTC), + expect: NewInt64(1731758400000), + }, + { + when: time.Millisecond * 1500, + expect: NewInt64(1500), + }, { when: errors.New("error"), expect: NewError(errors.New("error")), @@ -97,6 +106,15 @@ func TestUnmarshal(t *testing.T) { when: NewString("a"), expect: "a", }, + { + when: NewInt64(1731758400000), + expect: time.Date(2024, time.November, 16, 12, 0, 0, 0, time.UTC), + }, + + { + when: NewInt64(1500), + expect: time.Millisecond * 1500, + }, { when: NewError(errors.New("error")), expect: errors.New("error"), diff --git a/pkg/types/time.go b/pkg/types/time.go new file mode 100644 index 00000000..59550540 --- /dev/null +++ b/pkg/types/time.go @@ -0,0 +1,99 @@ +package types + +import ( + "github.com/pkg/errors" + "github.com/siyul-park/uniflow/pkg/encoding" + "reflect" + "time" + "unsafe" +) + +func newTimeEncoder() encoding.EncodeCompiler[any, Value] { + typeTime := reflect.TypeOf((*time.Time)(nil)).Elem() + + return encoding.EncodeCompilerFunc[any, Value](func(typ reflect.Type) (encoding.Encoder[any, Value], error) { + if typ != nil && typ == typeTime { + return encoding.EncodeFunc(func(source any) (Value, error) { + s := source.(time.Time) + return NewInt64(s.UnixMilli()), nil + }), nil + } + return nil, errors.WithStack(encoding.ErrUnsupportedType) + }) +} + +func newTimeDecoder() encoding.DecodeCompiler[Value] { + typeTime := reflect.TypeOf((*time.Time)(nil)).Elem() + + return encoding.DecodeCompilerFunc[Value](func(typ reflect.Type) (encoding.Decoder[Value, unsafe.Pointer], error) { + if typ != nil && typ.Kind() == reflect.Pointer { + if typ.Elem() == typeTime { + return encoding.DecodeFunc(func(source Value, target unsafe.Pointer) error { + var v time.Time + var err error + if s, ok := source.(String); ok { + v, err = time.Parse(time.RFC3339, s.String()) + } else if s, ok := source.(Integer); ok { + v = time.UnixMilli(s.Int()).UTC() + } else if s, ok := source.(Float); ok { + v = time.UnixMilli(int64(s.Float())).UTC() + } else { + err = errors.WithStack(encoding.ErrUnsupportedType) + } + if err != nil { + return err + } + t := reflect.NewAt(typ.Elem(), target) + t.Elem().Set(reflect.ValueOf(v)) + return nil + }), nil + } + } + return nil, errors.WithStack(encoding.ErrUnsupportedType) + }) +} + +func newDurationEncoder() encoding.EncodeCompiler[any, Value] { + typeDuration := reflect.TypeOf((*time.Duration)(nil)).Elem() + + return encoding.EncodeCompilerFunc[any, Value](func(typ reflect.Type) (encoding.Encoder[any, Value], error) { + if typ != nil && typ == typeDuration { + return encoding.EncodeFunc(func(source any) (Value, error) { + s := source.(time.Duration) + return NewInt64(s.Milliseconds()), nil + }), nil + } + return nil, errors.WithStack(encoding.ErrUnsupportedType) + }) +} + +func newDurationDecoder() encoding.DecodeCompiler[Value] { + typeDuration := reflect.TypeOf((*time.Duration)(nil)).Elem() + + return encoding.DecodeCompilerFunc[Value](func(typ reflect.Type) (encoding.Decoder[Value, unsafe.Pointer], error) { + if typ != nil && typ.Kind() == reflect.Pointer { + if typ.Elem() == typeDuration { + return encoding.DecodeFunc(func(source Value, target unsafe.Pointer) error { + var v time.Duration + var err error + if s, ok := source.(String); ok { + v, err = time.ParseDuration(s.String()) + } else if s, ok := source.(Integer); ok { + v = time.Millisecond * (time.Duration)(s.Int()) + } else if s, ok := source.(Float); ok { + v = time.Millisecond * (time.Duration)(s.Float()) + } else { + err = errors.WithStack(encoding.ErrUnsupportedType) + } + if err != nil { + return err + } + t := reflect.NewAt(typ.Elem(), target) + t.Elem().Set(reflect.ValueOf(v)) + return nil + }), nil + } + } + return nil, errors.WithStack(encoding.ErrUnsupportedType) + }) +} diff --git a/pkg/types/time_test.go b/pkg/types/time_test.go new file mode 100644 index 00000000..b99cabc7 --- /dev/null +++ b/pkg/types/time_test.go @@ -0,0 +1,60 @@ +package types + +import ( + "github.com/siyul-park/uniflow/pkg/encoding" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestTime_Encode(t *testing.T) { + enc := encoding.NewEncodeAssembler[any, Value]() + enc.Add(newTimeEncoder()) + + timestamp := time.Date(2024, time.November, 16, 12, 0, 0, 0, time.UTC) + + encoded, err := enc.Encode(timestamp) + assert.NoError(t, err) + + expected := NewInt64(timestamp.UnixMilli()) + assert.Equal(t, expected, encoded) +} + +func TestTime_Decode(t *testing.T) { + dec := encoding.NewDecodeAssembler[Value, any]() + dec.Add(newTimeDecoder()) + + timestamp := time.Date(2024, time.November, 16, 12, 0, 0, 0, time.UTC) + encoded := NewInt64(timestamp.UnixMilli()) + + var decoded time.Time + err := dec.Decode(encoded, &decoded) + assert.NoError(t, err) + assert.Equal(t, timestamp, decoded) +} + +func TestDuration_Encode(t *testing.T) { + enc := encoding.NewEncodeAssembler[any, Value]() + enc.Add(newDurationEncoder()) + + duration := 1500 * time.Millisecond + + encoded, err := enc.Encode(duration) + assert.NoError(t, err) + + expected := NewInt64(1500) + assert.Equal(t, expected, encoded) +} + +func TestDuration_Decode(t *testing.T) { + dec := encoding.NewDecodeAssembler[Value, any]() + dec.Add(newDurationDecoder()) + + duration := 1500 * time.Millisecond + encoded := NewInt64(1500) + + var decoded time.Duration + err := dec.Decode(encoded, &decoded) + assert.NoError(t, err) + assert.Equal(t, duration, decoded) +}