Skip to content

Commit dd896df

Browse files
committed
Compare with Goverter
1 parent 447d11b commit dd896df

File tree

9 files changed

+370
-2
lines changed

9 files changed

+370
-2
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,15 @@ generating type conversion code, it relies on comment-based directives that are
7373
not validated at compile time. Moreover, because it stops at the first error,
7474
refactoring becomes difficult when target types change. In contrast, Wire offers
7575
type-safe configuration and detailed diagnostics, but focuses on dependency
76-
injection. Convgen combines the best of both worlds, bringing **type-safe
77-
configuration** and **comprehensive diagnostics** to
76+
injection.
77+
78+
Convgen combines the best of both worlds, bringing **type-safe configuration**
79+
and **comprehensive diagnostics** to
7880
**type conversion code generation**.
7981

82+
To compare Convgen and goverter, see
83+
[cmd/vs-goverter/README.md](cmd/vs-goverter/README.md).
84+
8085
## Quick Start
8186

8287
1. Install Convgen:

cmd/vs-goverter/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Convgen vs. Goverter
2+
3+
This directory contains code to compare
4+
[Convgen](https://github.com/sublee/convgen) and
5+
[Goverter](https://github.com/jmattheis/goverter).
6+
7+
Generated code is already checked in. But you can regenerate it by running:
8+
9+
```bash
10+
go run ./cmd/convgen ./cmd/vs-goverter
11+
go run github.com/jmattheis/goverter/cmd/goverter@v1.9.2 gen ./cmd/vs-goverter
12+
```
13+
14+
The program output is:
15+
16+
```
17+
# Case 1: Successful conversion
18+
19+
Input:
20+
{ID:499602d2 Name:John Doe URLs:[https://example.com] Role:2 CreateTime:2025-01-02 03:04:05 +0000 UTC}
21+
Convgen:
22+
{"id":"499602d2","firstname":"John","lastname":"Doe","urls":["https://example.com"],"role":"member","createdAt":1735787045}
23+
Goverter:
24+
{"id":"499602d2","firstname":"John","lastname":"Doe","urls":["https://example.com"],"role":"member","createdAt":1735787045}
25+
26+
# Case 2: Comprehensive error message
27+
28+
Input:
29+
{ID:0 Name:Alice URLs:[] Role:0 CreateTime:0001-01-01 00:00:00 +0000 UTC}
30+
Convgen:
31+
converting User.Name: need two parts to parse firstname: "Alice"
32+
Goverter:
33+
need two parts to parse firstname: "Alice"
34+
```

cmd/vs-goverter/api/model.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
)
6+
7+
type User struct {
8+
Id string `json:"id"`
9+
Firstname string `json:"firstname"`
10+
Lastname string `json:"lastname"`
11+
Urls []string `json:"urls"`
12+
Role UserRole `json:"role"`
13+
CreatedAt int64 `json:"createdAt"`
14+
}
15+
16+
func (u User) String() string {
17+
b, _ := json.Marshal(u)
18+
return string(b)
19+
}
20+
21+
type UserRole int
22+
23+
const (
24+
UserRoleUnspecified UserRole = iota
25+
Admin
26+
Member
27+
Guest
28+
)
29+
30+
func (r UserRole) MarshalJSON() ([]byte, error) {
31+
switch r {
32+
case Admin:
33+
return json.Marshal("admin")
34+
case Member:
35+
return json.Marshal("member")
36+
case Guest:
37+
return json.Marshal("guest")
38+
}
39+
return json.Marshal(nil)
40+
}

cmd/vs-goverter/convgen.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//go:build convgen
2+
3+
package main
4+
5+
import (
6+
"time"
7+
8+
"github.com/sublee/convgen"
9+
"github.com/sublee/convgen/cmd/vs-goverter/api"
10+
)
11+
12+
var mod = convgen.Module(
13+
convgen.RenameReplace("", "", "Id", "ID"),
14+
convgen.RenameReplace("", "", "Url", "URL"),
15+
convgen.ImportFunc(func(u unique) string { return u.String() }),
16+
convgen.ImportFunc(func(t time.Time) int64 { return t.Unix() }),
17+
)
18+
19+
var ConvgenVersion = convgen.StructErr[User, api.User](mod,
20+
convgen.MatchFuncErr(User{}.Name, api.User{}.Firstname, firstname),
21+
convgen.MatchFuncErr(User{}.Name, api.User{}.Lastname, lastname),
22+
convgen.Match(User{}.CreateTime, api.User{}.CreatedAt),
23+
)
24+
25+
var convgenVersionUserRole = convgen.Enum[UserRole, api.UserRole](mod, api.UserRoleUnspecified,
26+
convgen.RenameTrimPrefix("UserRole", ""),
27+
convgen.MatchSkip(UserRoleUnknown, nil),
28+
)
29+
30+
// avoid unused error
31+
var _ = convgenVersionUserRole

cmd/vs-goverter/convgen_gen.go

Lines changed: 86 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/vs-goverter/goverter.gen.go

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/vs-goverter/goverter.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"time"
5+
6+
"github.com/sublee/convgen/cmd/vs-goverter/api"
7+
)
8+
9+
// goverter:variables
10+
// goverter:extend uniqueToString
11+
// goverter:extend timeToUnix
12+
// goverter:matchIgnoreCase
13+
var (
14+
// goverter:map Name Firstname | firstname
15+
// goverter:map Name Lastname | lastname
16+
// goverter:map CreateTime CreatedAt
17+
GoverterVersion func(User) (api.User, error)
18+
19+
// goverter:enum:unknown UserRoleUnspecified
20+
// goverter:enum:transform regex UserRole(\w+) $1
21+
// goverter:enum:map UserRoleUnknown UserRoleUnspecified
22+
GoverterVersionUserRole func(UserRole) api.UserRole
23+
)
24+
25+
func uniqueToString(u unique) string { return u.String() }
26+
27+
func timeToUnix(t time.Time) int64 { return t.Unix() }
28+
29+
var (
30+
// avoid unused error
31+
_ = uniqueToString
32+
_ = timeToUnix
33+
)

cmd/vs-goverter/main.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
func main() {
9+
fmt.Println("# Case 1: Successful conversion")
10+
fmt.Println()
11+
12+
u1 := User{
13+
ID: unique(1234567890),
14+
Name: "John Doe",
15+
URLs: []string{"https://example.com"},
16+
Role: UserRoleMember,
17+
CreateTime: time.Date(2025, 1, 2, 3, 4, 5, 0, time.UTC),
18+
}
19+
fmt.Println("Input:")
20+
fmt.Printf("\t%+v\n", u1)
21+
22+
fmt.Println("Convgen:")
23+
cgOut, _ := ConvgenVersion(u1)
24+
fmt.Printf("\t%s\n", cgOut)
25+
26+
fmt.Println("Goverter:")
27+
gvOut, _ := GoverterVersion(u1)
28+
fmt.Printf("\t%s\n", gvOut)
29+
30+
fmt.Println()
31+
32+
fmt.Println("# Case 2: Comprehensive error message")
33+
fmt.Println()
34+
35+
u2 := User{Name: "Alice"}
36+
fmt.Println("Input:")
37+
fmt.Printf("\t%+v\n", u2)
38+
39+
fmt.Println("Convgen:")
40+
_, cgErr := ConvgenVersion(u2)
41+
fmt.Printf("\t%s\n", cgErr.Error())
42+
43+
fmt.Println("Goverter:")
44+
_, gvErr := GoverterVersion(u2)
45+
fmt.Printf("\t%s\n", gvErr.Error())
46+
}

cmd/vs-goverter/model.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
"time"
8+
)
9+
10+
type User struct {
11+
ID unique
12+
Name string
13+
URLs []string
14+
Role UserRole
15+
CreateTime time.Time
16+
}
17+
18+
type unique int64
19+
20+
func (u unique) String() string { return strconv.FormatInt(int64(u), 16) }
21+
22+
func firstname(name string) (string, error) {
23+
parts := strings.Split(name, " ")
24+
if len(parts) != 2 {
25+
return "", fmt.Errorf("need two parts to parse firstname: %q", name)
26+
}
27+
return parts[0], nil
28+
}
29+
30+
func lastname(name string) (string, error) {
31+
parts := strings.Split(name, " ")
32+
if len(parts) != 2 {
33+
return "", fmt.Errorf("need two parts to parse firstname: %q", name)
34+
}
35+
return parts[1], nil
36+
}
37+
38+
type UserRole int
39+
40+
const (
41+
UserRoleUnknown UserRole = iota
42+
UserRoleAdmin
43+
UserRoleMember
44+
UserRoleGuest
45+
)

0 commit comments

Comments
 (0)