goverter

package module
v0.7.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2022 License: MIT Imports: 6 Imported by: 0

README

goverter

a "type-safe Go converter" generator

Build Status codecov Go Report Card Go Reference latest release

goverter is a tool for creating type-safe converters. All you have to do is create an interface and execute goverter. The project is meant as alternative to jinzhu/copier that doesn't use reflection.

Features

  • Automatic conversion of builtin types (house example), this includes:
    • slices, maps, named types, primitive types, pointers
    • structs with same fields
  • Extend parts of the conversion with your own implementation: Docs
  • Optional return of an error: Docs
  • Awesome error messages: mismatch type test
  • No reflection in the generated code

Usage

  1. Create a go modules project if you haven't done so already

    $ go mod init module-name
    
  2. Add goverter as dependency to your project

    $ go get github.com/polpo-studio/goverter
    
  3. Create your converter interface and mark it with a comment containing goverter:converter

    input.go

    package example
    
    // goverter:converter
    type Converter interface {
      Convert(source []Input) []Output
    }
    
    type Input struct {
      Name string
      Age int
    }
    type Output struct {
      Name string
      Age int
    }
    
  4. Run goverter:

    $ go run github.com/polpo-studio/goverter/cmd/goverter [package-name-in-full or relative-path-starts-with-a-dot]
    # example
    $ go run github.com/polpo-studio/goverter/cmd/goverter github.com/polpo-studio/goverter/example/simple
    $ go run github.com/polpo-studio/goverter/cmd/goverter ./example/simple
    

    Note that relative path must start with a dot to indicate it is not a golang package. Omiting the dot (e.g. example/simple) may not work.

  5. goverter created a file at ./generated/generated.go, it may look like this:

    package generated
    
    import simple "github.com/polpo-studio/goverter/example/simple"
    
    type ConverterImpl struct{}
    
    func (c *ConverterImpl) Convert(source []simple.Input) []simple.Output {
      simpleOutputList := make([]simple.Output, len(source))
      for i := 0; i < len(source); i++ {
        simpleOutputList[i] = c.simpleInputToSimpleOutput(source[i])
      }
      return simpleOutputList
    }
    func (c *ConverterImpl) simpleInputToSimpleOutput(source simple.Input) simple.Output {
      var simpleOutput simple.Output
      simpleOutput.Name = source.Name
      simpleOutput.Age = source.Age
      return simpleOutput
    }
    

Docs

Rename converter

With goverter:name you can set the name of the generated converter struct.

input.go

// goverter:converter
// goverter:name RenamedConverter
type BadlyNamed interface {
    // .. methods
}

output.go

type RenamedConverter struct {}

func (c *RenamedConverter) ...

Extend with custom implementation

With goverter:extend you can instruct goverter to use conversion methods from the same package as the conversion interface or from other packages. You can pass multiple extend statements to goverter:extend, or define the tag multiple times. Each statement can be:

  • a function in the package where the converter interface is declared:
    // goverter:extend SQLStringToPString
    
  • a function in another package. Use : to separate the package path from the function:
    // goverter:extend strconv:ParseBool
    // goverter:extend github.com/google/uuid:FromBytes
    

Note: The function name can be a regular expression.

// search for conversion methods that start with SQLStringTo in converter's package
// goverter:extend SQLStringTo.*
// the example below enables ParseBool method
// goverter:extend strconv:Parse.*

See house example sql.NullString

input.go

// goverter:converter
// goverter:extend IntToString
type Converter interface {
    Convert(Input) Output
}
type Input struct {Value int}
type Output struct {Value string}

// You must atleast define a source and target type. Meaning one parameter and one return.
// You can use any type you want, like struct, maps and so on.
func IntToString(i int) string {
    return fmt.Sprint(i)
}
Reuse generated converter

If you need access to the generated converter, you can define it as first parameter.

func IntToString(c Converter, i int) string {
    // c.DoSomething()..
    return fmt.Sprint(i)
}
Errors

Sometimes, custom conversion may fail, in this case goverter allows you to define a second return parameter which must be type error.

// goverter:converter
// goverter:extend IntToString
type Converter interface {
    Convert(Input) (Output, error)
}

type Input struct {Value int}
type Output struct {Value string}

func IntToString(i int) (string, error) {
    if i == 0 {
        return "", errors.New("zero is not allowed")
    }
    return fmt.Sprint(i)
}

Note: If you do this, methods on the interface that'll use this custom implementation, must also return error as second return.

Struct field mapping

With goverter:map you can map fields on structs that have the same type.

goverter:map takes 2 parameters.

  1. source field path (fields are separated by .)
  2. target field name
// goverter:converter
type Converter interface {
    // goverter:map Name FullName
    // goverter:map Nested.Age Age
    Convert(source Input) Output
}

type Input struct {
    Name string
    Nested NestedInput
}
type NestedInput struct {
    Age int
}
type Output struct {
    FullName string
    Age int
}

Case-insensitive field matching

With goverter:matchIgnoreCase tag you can instruct goverter to perform case-insensitive mapping between source and target fields. If this tag is present on a conversion method, goverter matches the fields using strings.EqualFold method.

Use this tag only when it is extremely unlikely for the source or the target to have two fields that differ only in casing. E.g.: converting go-jet generated model to protoc generated struct. If matchIgnoreCase is present and goverter detects an ambiquous match, it either prefers an exact match (if found) or reports an error. Use goverter:map to fix an ambiquous match error.

goverter:matchIgnoreCase takes no parameters.

// goverter:converter
type Converter interface {
    // goverter:matchIgnoreCase
    // goverter:map  FullId FullID
    Convert(source Input) Output
}

type Input struct {
    Uuid string
    FullId int
    fullId int
}
type Output struct {
    UUID string // auto-matched with Uuid due to goverter:matchIgnoreCase
    FullID string // mapped to FullId, to resolve ambiguity
}
Struct identity mapping

With goverter:mapIdentity you can instruct goverter to use the source struct as source for the conversion to the target property.

goverter:mapIdentity takes multiple field names separated by space .

// goverter:converter
type Converter interface {
    // goverter:mapIdentity Address
    ConvertPerson(source Person) APIPerson
}

type Person struct {
    Name   string
    Street string
    City   string
}

type APIPerson struct {
    Name    string
    Address APIAddress
}

type APIAddress struct {
    Street string
    City   string
}

In the example goverter will fill Address by creating a converter from Person to APIAddress. Example generated code:

func (c *ConverterImpl) ConvertPerson(source execution.Person) execution.APIPerson {
    var structsAPIPerson execution.APIPerson
    structsAPIPerson.Name = source.Name
    structsAPIPerson.Address = c.structsPersonToStructsAPIAddress(source)
    return structsAPIPerson
}
func (c *ConverterImpl) structsPersonToStructsAPIAddress(source execution.Person) execution.APIAddress {
    var structsAPIAddress execution.APIAddress
    structsAPIAddress.Street = source.Street
    structsAPIAddress.City = source.City
    return structsAPIAddress
}

Struct ignore field

With goverter:ignore you can ignore fields on the target struct

goverter:ignore takes multiple field names separated by space .

// goverter:converter
type Converter interface {
    // goverter:ignore Age
    Convert(source Input) Output
}

type Input struct {
    Name string
}
type Output struct {
    Name string
    Age int
}

Versioning

goverter use SemVer for versioning the cli.

License

This project is licensed under the MIT License - see the LICENSE file for details

Logo by MariaLetta

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GenerateConverter

func GenerateConverter(c GenerateConfig) ([]byte, error)

GenerateConverter generates converters.

func GenerateConverterFile

func GenerateConverterFile(fileName string, c GenerateConfig) error

GenerateConverterFile generates converters and writes them to a file.

Types

type GenerateConfig

type GenerateConfig struct {
	// PackageName is the package to use for the generated code.
	PackageName string
	// ScanDir is the package with golang files to scan for goverter tags.
	ScanDir string
	// ExtendMethods is a list of extensions to load in addition to goverter:extend statements
	// declared on the interface itself.
	ExtendMethods []string
	// WorkingDir is the working directory (usually the location of go.mod file), can be empty.
	WorkingDir string
	// PackagePath is the full package where the generated code is going to be stored, can be empty.
	// PackagePath is needed if goverter:extend tags use exported methods from the package where
	// generated code resides, making sure goverter does not create a loop import statement from
	// the generated code into its own package.
	PackagePath string
}

GenerateConfig the config for generating a converter.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL