diff --git a/mapper/enum_converter.go b/mapper/enum_converter.go new file mode 100644 index 0000000..4d3a268 --- /dev/null +++ b/mapper/enum_converter.go @@ -0,0 +1,74 @@ +package mapper + +import ( + "github.com/jinzhu/copier" +) + +type EnumTypeConverter[DTO ~string, MODEL ~int32] struct { + nameMap map[int32]string + valueMap map[string]int32 +} + +func NewEnumTypeConverter[DTO ~string, MODEL ~int32](nameMap map[int32]string, valueMap map[string]int32) *EnumTypeConverter[DTO, MODEL] { + return &EnumTypeConverter[DTO, MODEL]{ + valueMap: valueMap, + nameMap: nameMap, + } +} + +func (m *EnumTypeConverter[DTO, MODEL]) ToModel(dto *DTO) *MODEL { + if dto == nil { + return nil + } + + find, ok := m.valueMap[string(*dto)] + if !ok { + return nil + } + + model := MODEL(find) + return &model +} + +func (m *EnumTypeConverter[DTO, MODEL]) ToDto(model *MODEL) *DTO { + if model == nil { + return nil + } + + find, ok := m.nameMap[int32(*model)] + if !ok { + return nil + } + + dto := DTO(find) + return &dto +} + +func (m *EnumTypeConverter[DTO, MODEL]) NewConverterPair() []copier.TypeConverter { + srcType := MODEL(0) + dstType := DTO("") + + fromFn := m.ToDto + toFn := m.ToModel + + return NewGenericTypeConverterPair(&srcType, &dstType, fromFn, toFn) +} + +func NewGenericTypeConverterPair[A interface{}, B interface{}](srcType A, dstType B, fromFn func(src A) B, toFn func(src B) A) []copier.TypeConverter { + return []copier.TypeConverter{ + { + SrcType: srcType, + DstType: dstType, + Fn: func(src interface{}) (interface{}, error) { + return fromFn(src.(A)), nil + }, + }, + { + SrcType: dstType, + DstType: srcType, + Fn: func(src interface{}) (interface{}, error) { + return toFn(src.(B)), nil + }, + }, + } +} diff --git a/mapper/go.mod b/mapper/go.mod new file mode 100644 index 0000000..dbb362a --- /dev/null +++ b/mapper/go.mod @@ -0,0 +1,20 @@ +module github.com/tx7do/go-utils/mapper + +go 1.23.0 + +toolchain go1.24.3 + +require github.com/jinzhu/copier v0.4.0 + +require github.com/stretchr/testify v1.10.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/tx7do/go-utils => ../ diff --git a/mapper/go.sum b/mapper/go.sum new file mode 100644 index 0000000..5505dfc --- /dev/null +++ b/mapper/go.sum @@ -0,0 +1,25 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/mapper/interface.go b/mapper/interface.go new file mode 100644 index 0000000..0d2bb4c --- /dev/null +++ b/mapper/interface.go @@ -0,0 +1,10 @@ +package mapper + +// Mapper defines the interface for converting between DTOs and models. +type Mapper[DTO any, MODEL any] interface { + // ToModel converts a DTO to a MODEL. + ToModel(*DTO) *MODEL + + // ToDto converts a MODEL to a DTO. + ToDto(*MODEL) *DTO +} diff --git a/mapper/mapper.go b/mapper/mapper.go new file mode 100644 index 0000000..0db0691 --- /dev/null +++ b/mapper/mapper.go @@ -0,0 +1,51 @@ +package mapper + +import ( + "github.com/jinzhu/copier" +) + +type CopierMapper[DTO any, MODEL any] struct { + copierOption copier.Option +} + +func NewCopierMapper[DTO any, MODEL any]() *CopierMapper[DTO, MODEL] { + return &CopierMapper[DTO, MODEL]{ + copierOption: copier.Option{ + Converters: []copier.TypeConverter{}, + }, + } +} + +func (m *CopierMapper[DTO, MODEL]) AppendConverter(converter copier.TypeConverter) { + m.copierOption.Converters = append(m.copierOption.Converters, converter) +} + +func (m *CopierMapper[DTO, MODEL]) AppendConverters(converters []copier.TypeConverter) { + m.copierOption.Converters = append(m.copierOption.Converters, converters...) +} + +func (m *CopierMapper[DTO, MODEL]) ToModel(dto *DTO) *MODEL { + if dto == nil { + return nil + } + + var model MODEL + if err := copier.CopyWithOption(&model, dto, m.copierOption); err != nil { + panic(err) // Handle error appropriately in production code + } + + return &model +} + +func (m *CopierMapper[DTO, MODEL]) ToDto(model *MODEL) *DTO { + if model == nil { + return nil + } + + var dto DTO + if err := copier.CopyWithOption(&dto, model, m.copierOption); err != nil { + panic(err) // Handle error appropriately in production code + } + + return &dto +} diff --git a/mapper/mapper_test.go b/mapper/mapper_test.go new file mode 100644 index 0000000..30b88ff --- /dev/null +++ b/mapper/mapper_test.go @@ -0,0 +1,93 @@ +package mapper + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCopierMapper(t *testing.T) { + type DtoType struct { + Name string + Age int + } + + type ModelType struct { + Name string + Age int + } + + mapper := NewCopierMapper[DtoType, ModelType]() + + // 测试 ToModel 方法 + dto := &DtoType{Name: "Alice", Age: 25} + model := mapper.ToModel(dto) + assert.NotNil(t, model) + assert.Equal(t, "Alice", model.Name) + assert.Equal(t, 25, model.Age) + + // 测试 ToModel 方法,传入 nil + modelNil := mapper.ToModel(nil) + assert.Nil(t, modelNil) + + // 测试 ToDto 方法 + model = &ModelType{Name: "Bob", Age: 30} + dtoResult := mapper.ToDto(model) + assert.NotNil(t, dtoResult) + assert.Equal(t, "Bob", dtoResult.Name) + assert.Equal(t, 30, dtoResult.Age) + + // 测试 ToDto 方法,传入 nil + dtoNil := mapper.ToDto(nil) + assert.Nil(t, dtoNil) +} + +func TestEnumTypeConverter(t *testing.T) { + type DtoType string + type ModelType int32 + + const ( + DtoTypeOne DtoType = "One" + DtoTypeTwo DtoType = "Two" + ) + + const ( + ModelTypeOne ModelType = 1 + ModelTypeTwo ModelType = 2 + ) + + nameMap := map[int32]string{ + 1: "One", + 2: "Two", + } + valueMap := map[string]int32{ + "One": 1, + "Two": 2, + } + + converter := NewEnumTypeConverter[DtoType, ModelType](nameMap, valueMap) + + // 测试 ToModel 方法 + dto := DtoTypeOne + model := converter.ToModel(&dto) + assert.NotNil(t, model) + assert.Equal(t, int32(1), int32(*model)) + + // 测试 ToModel 方法,传入不存在的值 + dtoInvalid := DtoType("Three") + modelInvalid := converter.ToModel(&dtoInvalid) + assert.Nil(t, modelInvalid) + + // 测试 ToDto 方法 + tmpModelTwo := ModelTypeTwo + model = &tmpModelTwo + dtoResult := converter.ToDto(model) + assert.NotNil(t, dtoResult) + assert.Equal(t, "Two", string(*dtoResult)) + + // 测试 ToDto 方法,传入不存在的值 + tmpModelThree := ModelType(3) + modelInvalid = &tmpModelThree + dtoInvalidResult := converter.ToDto(modelInvalid) + assert.Nil(t, dtoInvalidResult) +} diff --git a/tag.bat b/tag.bat index edf9075..0707150 100644 --- a/tag.bat +++ b/tag.bat @@ -8,6 +8,7 @@ git tag jwtutil/v0.0.2 git tag id/v0.0.2 git tag slug/v0.0.1 git tag name_generator/v0.0.1 +git tag mapper/v0.0.1 git tag entgo/v1.1.31 git tag gorm/v1.1.6