convergen
Feature rich type-to-type copy code generator
About convergen
This large README is shown as a plain-text preview. Read the full README at the source
Convergen
=========
[](https://pkg.go.dev/github.com/reedom/convergen)
[](https://goreportcard.com/report/github.com/reedom/convergen)

Convergen is a code generator that creates functions for type-to-type copy.
It generates functions that copy field to field between two types.
Notation Table
--------------
| notation | location | summary |
|-------------------------------------------|--------------------|---------------------------------------------------------------------------------------|
| :match <`name` | `none`> | interface, method | Sets the field matcher algorithm (default: `name`). |
| :style <`return` | `arg`> | interface, method | Sets the style of the assignee variable input/output (default: `return`). |
| :recv <_var_> | method | Specifies the source value as a receiver of the generated function. |
| :reverse | method | Reverses the copy direction. Might be useful with receiver form. |
| :case | interface, method | Sets case-sensitive for name match (default). |
| :case:off | interface, method | Sets case-insensitive for name match. |
| :getter | interface, method | Includes getters for name match. |
| :getter:off | interface, method | Excludes getters for name match (default). |
| :stringer | interface, method | Calls String() if appropriate in name match. |
| :stringer:off | interface, method | Calls String() if appropriate in name match (default). |
| :typecast | interface, method | Allows type casting if appropriate in name match. |
| :typecast:off | interface, method | Suppresses type casting if appropriate in name match (default). |
| :skip <_dst field pattern_> | method | Marks the destination field to skip copying. Regex is allowed in /…/ syntax. |
| :map <_src_> <_dst field_> | method | the pair as assign source and destination. |
| :conv <_func_> <_src_> [_to field_] | method | Converts the source value by the converter and assigns its result to the destination. |
| :literal <_dst_> <_literal_> | method | Assigns the literal expression to the destination. |
| :preprocess <_func_> | method | Calls the function at the beginning of the convergen func. |
| :postprocess <_func_> | method | Calls the function at the end of the convergen function. |
Sample
------
To use Convergen, write a generator code in the following convention:
```go
//go:build convergen
package sample
import (
"time"
"github.com/sample/myapp/domain"
"github.com/sample/myapp/storage"
)
//go:generate go run github.com/reedom/[email protected]
type Convergen interface {
// :typecast
// :stringer
// :map Created.UnixMilli() Created
DomainToStorage(*domain.User) *storage.User
}
```
Convergen generates code similar to the following:
```go
// Code generated by github.com/reedom/convergen
// DO NOT EDIT.
package sample
import (
"time"
"github.com/sample/myapp/domain"
"github.com/sample/myapp/storage"
)
func DomainToStorage(src *domain.User) (dst *storage.User) {
dst = &storage.User{}
dst.ID = int64(src.ID)
dst.Name = src.Name
dst.Status = src.Status.String()
dst.Created = src.Created.UnixMilli()
return
}
```
for these struct types:
```go
package domain
import (
"time"
)
type User struct {
ID int
Name string
Status Status
Created time.Time
}
type Status string
func (s Status) String() string {
return string(s)
}
```
outputs:
```go
package storage
type User struct {
ID int64
Name string
Status string
Created int64
}
```
Installation and Introduction
-----------------------------
### Use as a Go generator
To use Convergen as a Go generator, install the module in your Go project directory via go get:
```shell
$ go get -u github.com/reedom/convergen@latest
```
Then, write a generator as follows:
```go
//go:generate go run github.com/reedom/[email protected]
type Convergen interface {
…
}
````
### Use as a CLI command
To use Convergen as a CLI command, install the command via go install:
```shell
$ go install github.com/reedom/convergen@latest
```
You can then generate code by calling:
```shell
$ convergen any-codegen-defined-code.go
```
The CLI help shows:
```shell
Usage: convergen [flags] <input path>
By default, the generated code is written to <input path>.gen.go
Flags:
-dry
Perform a dry run without writing files.
-log
Write log messages to <output path>.log.
-out string
Set the output file path.
-print
Print the resulting code to STDOUT as well.
```
Notations
---------
### `:convergen`
Use the `:convergen` notation to mark an interface as a converter definition.
By default, Convergen only looks for an interface named "`Convergen`" as a converter definition block.
You can use the `:convergen` notation to enable Convergen to recognize other interface names as well.
This is especially useful if you want to define methods with the same name but different receivers.
__Available locations__
interface
__Format__
```text
":convergen"
```
__Examples__
```go
// :convergen
type TransportConvergen interface {
// :recv t
ToDomain(*trans.Model) *domain.Model
}
// :convergen
type PersistentConvergen interface {
// :recv t
ToDomain(*persistent.Model) *domain.Model
}
```
### `:match <algorithm>`
Use the `:match` notation to set the field matcher algorithm.
__Default__
`:match name`
__Available locations__
interface, method
__Format__
```text
":match" <algorithm>
algorithm = "name" | none"
```
__Examples__
With `name` match, the generator matches fields or getter names (and their types) to generate
the conversion code.
```go
package model
type User struct {
ID int
Name string
}
```
```go
package web
type User struct {
id int
name string
}
func (u *User) ID() int {
return u.id
}
```
```go
// :match name
type Convergen interface {
ToStorage(*User) *storage.User
}
```
Convergen generates:
```go
func ToStorage(src *User) (dst *storage.User) {
dst := &storage.User{}
dst.ID = src.ID()
dst.Name = src.name
return
}
```
With `none` match, Convergen only processes fields or getters that have been explicitly
specified using `:map` and `:conv`.
### `:style <style>`
Use the `:style` notation to set the style of the assignee variable input/output.
__Default__
`:style return`
__Available locations__
interface, method
__Format__
```text
":style" style
style = "arg" | "return"
```
__Examples__
Examples of `return` style:
Basic:
```go
func ToStorage(src *domain.Pet) (dst *storage.Pet) {
```
With error:
```go
func ToStorage(src *domain.Pet) (dst *storage.Pet, err error) {
```
With receiver:
```go
func (src *domain.Pet) ToStorage() (dst *storage.Pet) {
```
Examples of `arg` style:
Basic:
```go
func ToStorage(dst *storage.Pet, src *domain.Pet) {
```
With error:
```go
func ToStorage(dst *storage.Pet, src *domain.Pet) (err error) {
```
With receiver:
```go
func (src *domain.Pet) ToStorage(dst *storage.Pet) {
```
### `:recv <var>`
Use the `:recv` notation to specify the source value as a receiver of the generated function.
According to the Go language specification, the receiver type must be defined in the same
package as the generated code.
By convention, the <_var_> should be the same identifier as the methods of the type defines.
__Default__
No receiver is used.
__Available locations__
method
__Format__
```text
":recv" var
var = variable-identifier
```
__Examples__
In the following example, assume that `domain.User` is defined in another file under the same
directory (package). It also assumes that other methods use `u` as their receiver variable name.
```go
package domain
import (
"github.com/sample/myapp/storage"
)
type Convergen interface {
// :recv u
ToStorage(*User) *storage.User
}
```
The generated code will be:
```go
package domain
import (
"github.com/sample/myapp/storage"
)
type User struct {
ID int
Name string
}
func (u *User) ToStorage() (dst *storage.User) {
dst = &storage.User{}
dst.ID = int64(u.ID)
dst.Name = u.Name
return
}
```
### `:reverse`
Reverse copy direction. Might be useful with receiver form.
To use `:reverse`, `:style arg` is required. (Otherwise it can't have any data source to copy from.)
Currently, `:reverse` not supported additional arguments.
__Default__
Copy in normal direction. In receiver form, receiver to a variable in argument.
__Available locations__
method
__Format__
```text
":reverse"
```
__Examples__
```go
package domain
import (
"github.com/sample/myapp/storage"
)
type Convergen interface {
// :style arg
// :recv u
// :reverse
FromStorage(*User) *storage.User
}
```
Will have:
```go
package domain
import (
"github.com/sample/myapp/storage"
)
type User struct {
ID int
Name string
}
func (u *User) FromStorage(src *storage.User) {
u.ID = int(src.User)
u.Name = src.Name
}
```
### `:case` / `:case:off`
This notation controls case-sensitive or case-insensitive matches in field and method names.
It is applicable to `:match name`, `:getter`, and `:skip` notations.
Other notations like `:map` and `:conv` retain case-sensitive matches.
__Default__
":case"
__Available locations__
interface, method
__Format__
```go
":case"
":case:off"
```
__Examples__
```go
// interface level notation makes ":case:off" as default.
// :case:off
type Convergen interface {
// Turn on case-sensitive match for names.
// :case
ToUserModel(*domain.User) storage.User
// Adopt the default, case-insensitive match in this case.
ToCategoryModel(*domain.Category) storage.Category
}
```
### `:getter` / `:getter:off`
Include getters for name match.
__Default__
`:getter:off`
__Available locations__
interface, method
__Format__
```text
":getter"
":getter:off"
```
__Examples__
With those models:
```go
package domain
type User struct {
name string
}
func (u *User) Name() string {
return u.name
}
```
```go
package storage
type User struct {
Name string
}
```
The default Convergen behaviour can't find the private `name` and won't notice the getter.
So, with the following we'll get…
```go
type Convergen interface {
ToStorageUser(*domain.User) *storage.User
}
````
```go
func ToStorageUser(src *domain.User) (dst *storage.User)
dst = &storage.User{}
// no match: dst.Name
return
}
```
And with `:getter` we'll have…
```go
type Convergen interface {
// :getter
ToStorageUser(*domain.User) *storage.User
}
````
```go
func ToStorageUser(src *domain.User) (dst *storage.User)
dst = &storage.User{}
dst.Name = src.Name()
return
}
```
Alternatively, you can get the same result with `:map`.
This is worth learning since `:getter` affects the entire method - `:map` allows you to get
the result selectively.
```go
type Convergen interface {
// :map Name() Name
ToStorageUser(*domain.User) *storage.User
}
```
### `:stringer` / `:stringer:off`
When matching field names, call the String() method of a custom type if it exists.
By default, Convergen has no way of knowing how to assign a custom type to a string.
Using the :stringer notation will tell Convergen to look for a String() method on any custom
types and use it when appropriate.
__Default__
`:stringer:off`
__Available locations__
interface, method
__Format__
```text
":stringer"
":stringer:off"
```
__Examples__
Consider the following code:
```go
package domain
type User struct {
Status Status
}
type Status struct {
status string
}
func (s Status) String() string {
return string(s)
}
var (
NotVerified = Status{"notVerified"}
Verified = Status{"verified"}
Invalidated = Status{"invalidated"}
)
```
```go
package storage
type User struct {
String string
}
```
Without any additional notations, Convergen has no idea how to assign the `Status` type to a string.
By adding `:stringer` notation to the Convergen interface, we're telling Convergen to look for a
`String()` method on any custom types and use it when appropriate:
```go
type Convergen interface {
// :stringer
ToStorageUser(*domain.User) *storage.User
}
```
Convergen will generate the following code:
```go
func ToStorageUser(src *domain.User) (dst *storage.User)
dst = &storage.User{}
dst.Status = src.Status.String()
return
}
```
Alternatively, you can achieve the same result with `:map`. However, `:stringer` affects
the entire method, while `:map` allows you to specify the fields to map selectively:
```go
type Convergen interface {
// :map Status.String() Name
ToStorageUser(*domain.User) *storage.User
}
```
### `:typecast`
Allow type casting if appropriate in name match.
__Default__
`:typecast:off`
__Available locations__
interface, method
__Format__
```text
":typecast"
":typecast:off"
```
__Examples__
With those models:
```go
package domain
type User struct {
ID int
Name string
Status Status
}
type Status string
```
```go
package storage
type User struct {
ID int64
Name string
Status string
}
```
Convergen respects types strictly. It will give up copying fields if their types do not match.
Note that Convergen relies on the [types.AssignableTo(V, T Type) bool][] method from the standard
packages. This means that the judgment is done by the type system of Go itself, not by a dumb
string type name match.
[types.AssignableTo(V, T Type) bool]: https://pkg.go.dev/go/types#AssignableTo
Without `:typecast` turned on:
```go
type Convergen interface {
ToDomainUser(*storage.User) *domain.User
}
````
We'll get:
```go
func ToDomainUser(src *storage.User) (dst *domain.User)
dst = &domain.User{}
// no match: dst.ID
dst.Name = src.Name
// no match: dst.Status
return
}
```
With `:typecast` it turned on:
```go
type Convergen interface {
// :typecast
ToDomainUser(*storage.User) *domain.User
}
````
```go
func ToDomainUser(src *storage.User) (dst *domain.User)
dst = &domain.User{}
dst.ID = int(src.ID)
dst.Name = src.Name
dst.Status = domain.Status(src.Status)
return
}
```
### `:skip <dst field pattern>`
Mark the destination field to skip copying.
A method can have multiple :skip lines that enable skipping multiple fields.
Other than field-path match, it accepts [regular expression][] match. To specify,
wrap the expression with `/`.
`:case` / `:case:off` affects `:skip`.
[regular expression]: https://github.com/google/re2/wiki/Syntax
__Available locations__
method
__Format__
```text
":skip" dst-field-pattern
dst-field-pattern = field-path | regexp
field-path = { identifier "." } identifier
regexp = "/" regular-expression "/"
```
__Examples__
Suppose we have the following domain and storage structs:
```go
package domain
type User struct {
ID int
Name string
Email string
Address Address
}
type Address struct {
Street string
City string
ZipCode string
}
```
If we want to skip copying the Name field of the storage.User struct,
we can use the :skip notation as follows:
```go
type Convergen interface {
// :skip Name
ToStorage(*domain.User) *storage.User
}
```
If we want to skip copying multiple fields, we can use multiple :skip notations:
```go
type Convergen interface {
// :skip Name
// :skip Email
ToStorage(*domain.User) *storage.User
}
```
We can also use regular expressions to match multiple fields:
```go
type Convergen interface {
// :skip /^Name|Email$/
ToStorage(*domain.User) *storage.User
}
```
This will result in the same generated code as the previous example.
### `:map <src> <dst field>`
Specify a field mapping rule.
When to use:
- copying a value between fields having different names.
- assigning a method's result value to a destination field.
- assigning additional argument value to a destination field.
A method can have multiple `:map` lines that enable mapping multiple fields.
`:case:off` does not affect `:map`;
<src> and <dst field> are compared in a case-sensitive manner.
__Available locations__
method
__Format__
```text
":map" src dst-field
src = field-or-method-chain | templated-value { "." field-or-getter-chain }
dst-field = field-path
field-path = { identifier "." } identifier
field-or-getter-chain = { (identifier | getter) "." } (identifier | getter)
getter = identifier "()"
templated-value = "$" number-of-argument
```
Arguments start from $1 for the first additional argument, $2 for the second, and so on.
__Examples__
In the following example, two fields have the same meaning but different names.
```go
package domain
type User struct {
ID int
Name string
}
```
```go
package storage
type User struct {
UserID int
Name string
}
```
We can use `:map` to connect them:
```go
type Convergen interface {
// Map the "ID" field in domain.User to the "UserID" field in storage.User.
// :map ID UserID
ToStorage(*domain.User) *storage.User
}
```
```go
func ToStorage(src *domain.User) (dst *storage.User) {
dst = storage.User{}
dst.UserID = src.ID
dst.Name = src.Name
return
}
```
In the following example, Status is a custom type with a method to retrieve its raw value.
```go
package domain
type User struct {
ID int
Name string
Status Status
}
type Status int
func (s Status) Int() int {
return int(s)
}
var (
NotVerified = Status(1)
Verified = Status(2)
Invalidated = Status(3)
)
```
```go
package storage
type User struct {
UserID int
Name string
Status int
}
```
We can use `:map` to apply the method's return value to assign:
```go
type Convergen interface {
// Map the "ID" field in domain.User to the "UserID" field in storage.User.
// Map the result of the "Status.Int()" method in domain.User to the "Status" field in storage.User.
// :map ID UserID
// :map Status.Int() Status
ToStorage(*domain.User) *storage.User
}
```
```go
func ToStorage(src *domain.User) (dst *storage.User) {
dst = storage.User{}
dst.UserID = src.ID
dst.Name = src.Name
dst.Status = src.Status.Int()
return
}
```
We can use `:map` to apply the additional arguments to assign:
```go
type Convergen interface {
// Map the "ID" field in domain.User to the "UserID" field in storage.User.Frequently Asked Questions
What is convergen?
convergen is a Generators library for the Go programming language. Feature rich type-to-type copy code generator
How do I install convergen?
Install convergen with the Go module system using `go get reedom/convergen`. Check the repository for the current installation instructions.
What category does convergen belong to?
convergen is listed under Generators, specifically Generators.