Categorygithub.com/underbek/datamapper
modulepackage
0.0.14
Repository: https://github.com/underbek/datamapper.git
Documentation: pkg.go.dev

# README

Datamapper

Go Report Card Latest Release

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