# README
Datamapper

Datamapper is a library written in Golang. It allows you to convert models to each other using tags. If you are tired of writing a lot of converters for structures, you will definitely like Datamapper :)
Getting started
Install
Let's start by installing Datamapper:
go install github.com/underbek/datamapper@latest
Usage
Datamapper provides the ability to customize model conversion using flags:
Usage:
datamapper [OPTIONS]
Application Options:
-c, --config= Yaml config path
-v, --version Current version
-d, --destination= Destination file path
--cf= User conversion functions sources/packages. Can add package alias like {package_path}:{alias)
--from= Model from name
--from-tag= Model from tag (default: map)
--from-source= From model source/package. Can add package alias like {package_path}:{alias) (default: .)
--to= Model to name
--to-tag= Model to tag (default: map)
--to-source= To model source/package. Can add package alias like {package_path}:{alias) (default: .)
-i, --inverse Create direct and inverse conversions
-s, --with-slice Create convertors with slice
-r, --recursive Parse recursive fields and create conversion if it not exists
-p, --with-pointers If field is pointer and recursive flag enabled then create convertors with pointers
Help Options:
-h, --help Show this help message
Config:
Datamapper can read configuration file by config
flag:
Config example:
# array of conversion functions
conversion-functions:
## source path or full package name
- source: github.com/underbek/datamapper/_test_data/mapper/convertors
## optional package alias
alias: cf
- source: github.com/underbek/datamapper/_test_data/mapper/other_convertors
# array of conversion mapping
options:
## From model
- from:
## name of model (can use with pointer)
name: "*User"
## mapping tag (optional|default = map)
tag : map
## source path or full package name
source: github.com/underbek/datamapper/_test_data/mapper/domain
## optional package alias
alias: domain
## From model like a from model
to:
name: "User"
source: github.com/underbek/datamapper/_test_data/mapper/transport
## Destination file path
destination: _test_data/local_test/domain_to_dto_user_converter.go
## If you need to crate inverse conversions
inverse: true
## Parse recursive fields and create conversion if it not exists (default = false)
recursive: false
## If field is pointer and recursive flag enabled then create convertors with pointers (default = false)
with-pointers: false
## Create convertors for slices (default = false)
with-slice: true
- from:
name: "User"
source: github.com/underbek/datamapper/_test_data/mapper/broken
alias: bk
tag: map
to:
name: "*User"
source: github.com/underbek/datamapper/_test_data/mapper/domain
alias: dm
tag: map
destination: _test_data/local_test/broken_to_domain_user_converter.go
inverse: true
with-slice: true
Conversion functions
Datamapper already has converters for basic types. You can look into them here. In addition, you can use your own converters:
- by types
package conversion
import "fmt"
func ConvertIntToString(from int) string {
return fmt.Sprint(from)
}
- by generics
package conversion
import "fmt"
func ConvertAnyToString[T int | uint | float32](from T) string {
return fmt.Sprint(from)
}
func ConvertStringToMany[T int | uint | float32](from int) T {
return T(from)
}
func ConvertAnyToMany[T, V int | uint | float32](from T) V {
return V(from)
}
- with error
package converts
import "github.com/shopspring/decimal"
func ConvertStringToDecimal(from string) (decimal.Decimal, error) {
return decimal.NewFromString(from)
}
Features
- Parse and filter tag
- Generate empty convertor
- Map similar types
- Simple convertor test
- Create conversion functions
- Use conversion functions in convertor
- Parse conversion functions from sources
- Parse generic types from other package (constrains.Float)
- Parse conversion functions with generic from
- Parse conversion functions with generic to
- Parse conversion functions with generic from and to
- Parse conversion functions with generic struct
- Parse conversion functions with struct
- Create base conversation source
- Generate convertors by other package models
- Generate convertors by other package fields in models
- Generate convertors by same package conversion functions
- Fix tests
- Delete package flag
- First mapper tests
- Use other conversion functions in convertor
- Use conversion functions with error -> convertor with error
- Convert with pointer field
- Convert with pointer field with error
- No nil err if from and to fields are pointers
- Add CI with tests and linters
- Parse other package
- First console generate
- Set default options
- Add generation info
- Fill readme
- First release
- Get first value by tag
- Move main to root
- Alias package name for (from/to) models and custom cf
- Use conversion functions with pointers
- Use some conversion functions sources
- Parse type alias
- Parse array, slice, map
- Generate convertors by slice fields
- New alias options (alias to each convertors' path)
- Converts both ways in one source
- Generate convertor with from/to pointer
- Fix error by parsing function as member
- Wrap errors
- Parse packages with broken sources
- Recursive convert by option if not found conversions
- Parse user struct in struct
- Use generated convertors in convertor like conversion function
- Generator must be return function model
- Use conversion functions from datamapper package without parsing
- Update readme
- Use one destination for models convertors by recursive flag
- Map field without tag
- Generate convertors with map fields
- Generate convertors with array fields
- Option for default field value if from field is nil
- Parse comments
- Parse embed struct
- Parse func aliases
- Warning or error politics if tags is not equals
- Fill some conversion functions
- Copy using conversion functions from datamapper to target service if flag set
- Parse custom error by conversion functions
- Fix cyclop linter
Example of the code generated by the library
// Code generated by datamapper.
// https://github.com/underbek/datamapper
// Package local_test is a generated datamapper package.
package local_test
import (
"errors"
"fmt"
cf "github.com/underbek/datamapper/_test_data/mapper/convertors"
domain "github.com/underbek/datamapper/_test_data/mapper/domain"
"github.com/underbek/datamapper/_test_data/mapper/other_convertors"
"github.com/underbek/datamapper/_test_data/mapper/transport"
"github.com/underbek/datamapper/converts"
)
// ConvertDomainUserToTransportUser convert *domain.User by tag map to transport.User by tag map
func ConvertDomainUserToTransportUser(from *domain.User) (transport.User, error) {
if from == nil {
return transport.User{}, errors.New("User is nil")
}
var fromChildCount *string
if from.ChildCount != nil {
res := converts.ConvertNumericToString(*from.ChildCount)
fromChildCount = &res
}
return transport.User{
UUID: other_convertors.CustomIntegerToUUID(from.ID),
Name: from.Name,
Age: converts.ConvertDecimalToString(from.Age),
ChildCount: fromChildCount,
}, nil
}
// ConvertDomainUserSliceToTransportUserSlice convert []*domain.User to []transport.User
func ConvertDomainUserSliceToTransportUserSlice(fromSlice []*domain.User) ([]transport.User, error) {
if fromSlice == nil {
return nil, nil
}
toSlice := make([]transport.User, 0, len(fromSlice))
for _, from := range fromSlice {
to, err := ConvertDomainUserToTransportUser(from)
if err != nil {
return nil, fmt.Errorf("convert []*domain.User to []transport.User failed: %w", err)
}
toSlice = append(toSlice, to)
}
return toSlice, nil
}
// ConvertTransportUserToDomainUser convert transport.User by tag map to *domain.User by tag map
func ConvertTransportUserToDomainUser(from transport.User) (*domain.User, error) {
fromAge, err := converts.ConvertStringToDecimal(from.Age)
if err != nil {
return nil, fmt.Errorf("convert User.Age -> User.Age failed: %w", err)
}
var fromChildCount *int
if from.ChildCount != nil {
res, err := converts.ConvertStringToSigned[int](*from.ChildCount)
if err != nil {
return nil, fmt.Errorf("convert User.ChildCount -> User.ChildCount failed: %w", err)
}
fromChildCount = &res
}
return &domain.User{
ID: cf.CustomUUIDToInteger[int](from.UUID),
Name: from.Name,
Age: fromAge,
ChildCount: fromChildCount,
}, nil
}
// ConvertTransportUserSliceToDomainUserSlice convert []transport.User to []*domain.User
func ConvertTransportUserSliceToDomainUserSlice(fromSlice []transport.User) ([]*domain.User, error) {
if fromSlice == nil {
return nil, nil
}
toSlice := make([]*domain.User, 0, len(fromSlice))
for _, from := range fromSlice {
to, err := ConvertTransportUserToDomainUser(from)
if err != nil {
return nil, fmt.Errorf("convert []transport.User to []*domain.User failed: %w", err)
}
toSlice = append(toSlice, to)
}
return toSlice, nil
}
# Packages
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author