From dc339b19d5ff4e2405816c2fe655add77bec47b2 Mon Sep 17 00:00:00 2001 From: dimacgka Date: Wed, 7 Aug 2024 10:03:48 +0300 Subject: [PATCH 1/2] [+] added functional options for auto mapping --- auto.go | 29 ++++++++++++++++------------- auto_test.go | 18 +++++++++++++++--- options.go | 24 ++++++++++++++---------- 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/auto.go b/auto.go index 77ac9da..3863839 100644 --- a/auto.go +++ b/auto.go @@ -10,14 +10,17 @@ var ( manualFieldRoutes = map[reflect.Type]map[string]string{} ) -func AutoRoute[TSource, TDest any | []any](options ...AutoMapperOption) error { +func AutoRoute[TSource, TDest any | []any](opts ...Option) error { s := new(TSource) d := new(TDest) sourceStorage, _ := fmap.GetFrom(s) destStorage, _ := fmap.GetFrom(d) sourceType := reflect.TypeOf(s) - parseOptions(options, sourceType) + opt := &options{} + for _, o := range opts { + o.apply(opt) + } mapFunc := func(source TSource, dest *TDest) error { for _, sourcePath := range sourceStorage.GetAllPaths() { @@ -27,26 +30,26 @@ func AutoRoute[TSource, TDest any | []any](options ...AutoMapperOption) error { } srcFld := sourceStorage.MustFind(sourcePath) + if destFld.GetType() != srcFld.GetType() { + continue + } if err := setFieldRecursive(srcFld, destFld, source, dest); err != nil { return err } } - return nil - } - return AddRoute[TSource, TDest](mapFunc) -} -func parseOptions(options []AutoMapperOption, sourceType reflect.Type) { - for _, option := range options { - switch autoMapperOption := option.(type) { - case fieldPathOption: - if manualFieldRoutes[sourceType] == nil { - manualFieldRoutes[sourceType] = map[string]string{} + for _, o := range opt.Fns { + fn, ok := o.(func(TSource, *TDest)) + if !ok { + continue } - manualFieldRoutes[sourceType][autoMapperOption.source] = autoMapperOption.dest + fn(source, dest) } + return nil } + + return AddRoute[TSource, TDest](mapFunc) } func setFieldRecursive(sourceFld, destFld fmap.Field, source, dest any) error { diff --git a/auto_test.go b/auto_test.go index 531288f..2dd67d9 100644 --- a/auto_test.go +++ b/auto_test.go @@ -48,6 +48,7 @@ type DeepNestedStructDest struct { func TestAutoRoute(t *testing.T) { _ = AutoRoute[AutoMappingStructSource, AutoMappingStructDest]() + _ = AutoRoute[TestingStructSource, TestingStructDest]() t.Run("Auto route without options", func(t *testing.T) { ptrTime := time.Now() ptrUuid := uuid.New() @@ -60,13 +61,24 @@ func TestAutoRoute(t *testing.T) { assert.Equal(t, source.PtrUUID, dest.PtrUUID) assert.Equal(t, source.PtrTime, dest.PtrTime) }) - _ = AutoRoute[AutoMappingStructSource, AutoMappingStructDest](WithFieldRoute("Name", "SecondName")) + timeNow := time.Now() + _ = AutoRoute[AutoMappingStructSource, AutoMappingStructDest](WithFunc(func(source AutoMappingStructSource, dest *AutoMappingStructDest) { + if source.Name == "Test1" { + dest.SecondName = "Test2" + } + if source.PtrTime == nil { + dest.PtrTime = &timeNow + } + dest.Time = timeNow + })) t.Run("Auto route with options", func(t *testing.T) { source := &AutoMappingStructSource{Name: "Test1"} dest, err := MapTo[AutoMappingStructDest](source) assert.NoError(t, err) - assert.Equal(t, "", dest.Name) - assert.Equal(t, source.Name, dest.SecondName) + assert.Equal(t, source.Name, dest.Name) + assert.Equal(t, "Test2", dest.SecondName) + assert.Equal(t, timeNow, *dest.PtrTime) + assert.Equal(t, timeNow, dest.Time) }) t.Run("Auto mapping struct fields", func(t *testing.T) { source := &AutoMappingStructSource{ diff --git a/options.go b/options.go index b892876..c3a8da3 100644 --- a/options.go +++ b/options.go @@ -1,17 +1,21 @@ package gomapper -type AutoMapperOption interface { +type withFuncOption[TSource, TDest any] struct { + fn func(TSource, *TDest) } -type fieldPathOption struct { - AutoMapperOption - source string - dest string +type options struct { + Fns []any } -func WithFieldRoute(source, dest string) AutoMapperOption { - return fieldPathOption{ - source: source, - dest: dest, - } +type Option interface { + apply(*options) +} + +func (a withFuncOption[TSource, TDest]) apply(opts *options) { + opts.Fns = append(opts.Fns, a.fn) +} + +func WithFunc[TSource, TDest any](fn func(TSource, *TDest)) Option { + return &withFuncOption[TSource, TDest]{fn: fn} } From 0e89bccded964d33c2442b777108fb30f7cd7c4e Mon Sep 17 00:00:00 2001 From: dimacgka Date: Tue, 3 Dec 2024 15:23:07 +0300 Subject: [PATCH 2/2] added new option for ignore field --- .github/workflows/go.yml | 2 +- auto.go | 4 ++++ auto_test.go | 17 ++++++++++++++--- go.mod | 4 ++-- go.sum | 4 ++-- options.go | 36 +++++++++++++++++++++++++++++++++++- 6 files changed, 58 insertions(+), 9 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 096d2ac..706de60 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - name: Build run: go build -v ./... diff --git a/auto.go b/auto.go index 3863839..70e283e 100644 --- a/auto.go +++ b/auto.go @@ -2,6 +2,7 @@ package gomapper import ( "reflect" + "slices" "github.com/insei/fmap/v3" ) @@ -33,6 +34,9 @@ func AutoRoute[TSource, TDest any | []any](opts ...Option) error { if destFld.GetType() != srcFld.GetType() { continue } + if slices.Contains(opt.Excluded, srcFld) { + continue + } if err := setFieldRecursive(srcFld, destFld, source, dest); err != nil { return err } diff --git a/auto_test.go b/auto_test.go index 2dd67d9..dd4b183 100644 --- a/auto_test.go +++ b/auto_test.go @@ -9,6 +9,7 @@ import ( ) type AutoMappingStructSource struct { + Age int Name string Time time.Time UUID uuid.UUID @@ -18,10 +19,11 @@ type AutoMappingStructSource struct { } type AutoMappingStructDest struct { + UUID uuid.UUID + Age int Name string SecondName string Time time.Time - UUID uuid.UUID PtrTime *time.Time PtrUUID *uuid.UUID NestedStruct NestedStructDest @@ -70,15 +72,24 @@ func TestAutoRoute(t *testing.T) { dest.PtrTime = &timeNow } dest.Time = timeNow - })) + }), + WithFieldSkip(func(dest *AutoMappingStructSource) any { + return &dest.UUID + }), + WithFieldSkip(func(dest *AutoMappingStructSource) any { + return &dest.Age + }), + ) t.Run("Auto route with options", func(t *testing.T) { - source := &AutoMappingStructSource{Name: "Test1"} + source := &AutoMappingStructSource{UUID: uuid.New(), Name: "Test1", Age: 25} dest, err := MapTo[AutoMappingStructDest](source) assert.NoError(t, err) assert.Equal(t, source.Name, dest.Name) assert.Equal(t, "Test2", dest.SecondName) assert.Equal(t, timeNow, *dest.PtrTime) assert.Equal(t, timeNow, dest.Time) + assert.Equal(t, 0, dest.Age) + assert.Equal(t, uuid.Nil, dest.UUID) }) t.Run("Auto mapping struct fields", func(t *testing.T) { source := &AutoMappingStructSource{ diff --git a/go.mod b/go.mod index 4463cea..077019d 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/insei/gomapper -go 1.18 +go 1.21 require ( github.com/google/uuid v1.6.0 - github.com/insei/fmap/v3 v3.0.0 + github.com/insei/fmap/v3 v3.1.2 github.com/stretchr/testify v1.9.0 ) diff --git a/go.sum b/go.sum index 3eb32d5..f44f3c7 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/insei/fmap/v3 v3.0.0 h1:BpRsFgQ2nt5tl/8tzp6y+MWIAJhqCTeKBHC56L/Um30= -github.com/insei/fmap/v3 v3.0.0/go.mod h1:Kk0gs7nKb4E/JycKJFnrsX5hlyBBe0yetGKFCJG0vzk= +github.com/insei/fmap/v3 v3.1.2 h1:ZBr+WiZpIxFNeMo2X4QOST4AFl0sGAkG+EO08Ved3bY= +github.com/insei/fmap/v3 v3.1.2/go.mod h1:Kk0gs7nKb4E/JycKJFnrsX5hlyBBe0yetGKFCJG0vzk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= diff --git a/options.go b/options.go index c3a8da3..a36734c 100644 --- a/options.go +++ b/options.go @@ -1,11 +1,20 @@ package gomapper +import ( + "github.com/insei/fmap/v3" +) + type withFuncOption[TSource, TDest any] struct { fn func(TSource, *TDest) } +type withFieldSkip[TDest any] struct { + field fmap.Field +} + type options struct { - Fns []any + Fns []any + Excluded []fmap.Field } type Option interface { @@ -16,6 +25,31 @@ func (a withFuncOption[TSource, TDest]) apply(opts *options) { opts.Fns = append(opts.Fns, a.fn) } +func (a withFieldSkip[TDest]) apply(opts *options) { + if opts.Excluded == nil { + opts.Excluded = make([]fmap.Field, 0) + } + opts.Excluded = append(opts.Excluded, a.field) +} + func WithFunc[TSource, TDest any](fn func(TSource, *TDest)) Option { return &withFuncOption[TSource, TDest]{fn: fn} } + +func WithFieldSkip[TSource any](fn func(*TSource) any) Option { + source := new(TSource) + + storage, err := fmap.GetFrom(source) + if err != nil { + panic(err) + } + + fieldPtr := fn(source) + + field, err := storage.GetFieldByPtr(source, fieldPtr) + if err != nil { + panic(err) + } + + return &withFieldSkip[TSource]{field: field} +}