Skip to content

Commit 90fa741

Browse files
committed
gentypes: Add support for third-party types
The gentypes tool was previously limited to generating optional types for local types that implemented the MarshalMsgpack and UnmarshalMsgpack methods. Thi made it impossible to generate optional types for types from external modules. This commit enhances the generator to support third-party types by introducing the following new flags: * -force: To bypass the check for MarshalMsgpack and UnmarshalMsgpack methods. * -imports: To add necessary imports for the third-party type and custom functions. * -marshal-func: To specify a custom marshal function. * -unmarshal-func: To specify a custom unmarshal function. The generator code has been refactored to use a GenerateOptions struct for better organization. Additionally, this commit: * Adds a new test case for generating an optional type for uuid.UUID. * Updates the README.md with documentation for the new flags and an example. * Moves test files to a more appropriate internal/test directory. Closes #TNTP-3734.
1 parent 93a7028 commit 90fa741

15 files changed

+779
-63
lines changed

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ linters:
5757
- "golang.org/x/tools"
5858
- "github.com/vmihailenco/msgpack/v5"
5959
- "github.com/tarantool/go-option"
60+
- "github.com/google/uuid"
6061
test:
6162
files:
6263
- "$test"

README.md

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,65 @@ Or you can use it to generate file from go:
6666

6767
Flags:
6868

69-
`-package`: Path to the Go package containing types to wrap (default: `"."`)
70-
`-ext-code`: MessagePack extension code to use for custom types (must be between
69+
* `-package`: Path to the Go package containing types to wrap (default: `"."`)
70+
* `-ext-code`: MessagePack extension code to use for custom types (must be between
7171
-128 and 127, no default value)
72-
`-verbose`: Enable verbose output (default: `false`)
72+
* `-verbose`: Enable verbose output (default: `false`)
73+
* `-force`: Ignore absence of marshal/unmarshal methods on type (default: `false`).
74+
Helpful for types from third-party modules.
75+
* `-imports`: Add imports to generated file (default is empty).
76+
Helpful for types from third-party modules.
77+
* `-marshal-func`: func that should do marshaling (default is `MarshalMsgpack` method).
78+
Helpful for types from third-party modules.
79+
Should be func of type `func(v T) ([]byte, error)` and should
80+
be located in the same dir or should be imported.
81+
* `-unmarshal-func`: func that should do unmarshalling (default is `UnmarshalMsgpack` method).
82+
Helpful for types from third-party modules.
83+
Should be func of type `func(v *T, data []byte) error` and should
84+
be located in the same dir or should be imported.
85+
86+
#### Generating Optional Types for Third-Party Modules
87+
88+
Sometimes you need to generate an optional type for a type from a third-party module,
89+
and you can't add `MarshalMsgpack`/`UnmarshalMsgpack` methods to it.
90+
In this case, you can use the `-force`, `-imports`, `-marshal-func`, and `-unmarshal-func` flags.
91+
92+
For example, to generate an optional type for `github.com/google/uuid.UUID`:
93+
94+
1. Create a file with marshal and unmarshal functions for the third-party type.
95+
For example, `uuid.go`:
96+
97+
```go
98+
package main
99+
100+
import (
101+
"errors"
102+
103+
"github.com/google/uuid"
104+
)
105+
106+
func encodeUUID(uuid uuid.UUID) ([]byte, error) {
107+
return uuid[:], nil
108+
}
109+
110+
var (
111+
ErrInvalidLength = errors.New("invalid length")
112+
)
113+
114+
func decodeUUID(uuid *uuid.UUID, data []byte) error {
115+
if len(data) != len(uuid) {
116+
return ErrInvalidLength
117+
}
118+
copy(uuid[:], data)
119+
return nil
120+
}
121+
```
122+
123+
2. Use the following `go:generate` command:
124+
125+
```go
126+
//go:generate go run github.com/tarantool/go-option/cmd/gentypes@latest -package . -imports "github.com/google/uuid" -type UUID -marshal-func "encodeUUID" -unmarshal-func "decodeUUID" -force -ext-code 100
127+
```
73128

74129
#### Using Generated Types
75130

cmd/gentypes/flag.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package main
2+
3+
import (
4+
"maps"
5+
"slices"
6+
"strings"
7+
)
8+
9+
type stringListFlag []string
10+
11+
func (s *stringListFlag) String() string {
12+
return strings.Join(*s, ", ")
13+
}
14+
15+
func (s *stringListFlag) Set(s2 string) error {
16+
*s = append(*s, s2)
17+
return nil
18+
}
19+
20+
func deleteDuplicates(s stringListFlag) stringListFlag {
21+
uniqMap := map[string]struct{}{}
22+
for _, val := range s {
23+
uniqMap[val] = struct{}{}
24+
}
25+
26+
return slices.Collect(maps.Keys(uniqMap))
27+
}
28+
29+
func (s *stringListFlag) Get() []string {
30+
deduped := deleteDuplicates(*s)
31+
slices.Sort(deduped)
32+
33+
return deduped
34+
}

cmd/gentypes/generate.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
//go:generate go run github.com/tarantool/go-option/cmd/gentypes -ext-code 1 -package test FullMsgpackExtType
1+
//go:generate go run github.com/tarantool/go-option/cmd/gentypes -ext-code 1 -package internal/test FullMsgpackExtType
2+
//go:generate go run github.com/tarantool/go-option/cmd/gentypes -ext-code 2 -force -package internal/test HiddenTypeAlias
3+
//go:generate go run github.com/tarantool/go-option/cmd/gentypes -ext-code 3 -imports github.com/google/uuid -package internal/test -marshal-func encodeUUID -unmarshal-func decodeUUID uuid.UUID
4+
25
package main

cmd/gentypes/generator/extension.go

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
_ "embed"
77
"fmt"
88
"strconv"
9+
"strings"
910
"text/template"
1011
)
1112

@@ -26,22 +27,72 @@ func InitializeTemplates() {
2627
cTypeGenTestTemplate = template.Must(template.New("type_gen_test.go.tpl").Parse(typeGenTestTemplate))
2728
}
2829

30+
const (
31+
maxNameParts = 2
32+
)
33+
34+
func constructTypeName(typeName string) string {
35+
splittedName := strings.SplitN(typeName, ".", maxNameParts)
36+
switch len(splittedName) {
37+
case 1:
38+
typeName = splittedName[0]
39+
case maxNameParts:
40+
typeName = splittedName[1]
41+
default:
42+
panic("invalid type name: " + typeName)
43+
}
44+
45+
return "Optional" + typeName
46+
}
47+
48+
// GenerateOptions is the options for the code generation.
49+
type GenerateOptions struct {
50+
// TypeName is the name of the type to generate optional to.
51+
TypeName string
52+
// ExtCode is the extension code.
53+
ExtCode int
54+
// PackageName is the name of the package to generate to.
55+
PackageName string
56+
// Imports is the list of imports to add to the generated code.
57+
Imports []string
58+
// CustomMarshalFunc is the name of the custom marshal function.
59+
CustomMarshalFunc string
60+
// CustomUnmarshalFunc is the name of the custom unmarshal function.
61+
CustomUnmarshalFunc string
62+
}
63+
2964
// GenerateByType generates the code for the optional type.
30-
func GenerateByType(typeName string, code int, packageName string) ([]byte, error) {
65+
func GenerateByType(opts GenerateOptions) ([]byte, error) {
3166
var buf bytes.Buffer
3267

68+
if opts.CustomMarshalFunc == "" {
69+
opts.CustomMarshalFunc = "o.value.MarshalMsgpack()"
70+
} else {
71+
opts.CustomMarshalFunc += "(o.value)"
72+
}
73+
74+
if opts.CustomUnmarshalFunc == "" {
75+
opts.CustomUnmarshalFunc = "o.value.UnmarshalMsgpack(a)"
76+
} else {
77+
opts.CustomUnmarshalFunc += "(&o.value, a)"
78+
}
79+
3380
err := cTypeGenTemplate.Execute(&buf, struct {
34-
Name string
35-
Type string
36-
ExtCode string
37-
PackageName string
38-
Imports []string
81+
Name string
82+
Type string
83+
ExtCode string
84+
PackageName string
85+
Imports []string
86+
CustomMarshalFunc string
87+
CustomUnmarshalFunc string
3988
}{
40-
Name: "Optional" + typeName,
41-
Type: typeName,
42-
ExtCode: strconv.Itoa(code),
43-
PackageName: packageName,
44-
Imports: nil,
89+
Name: constructTypeName(opts.TypeName),
90+
Type: opts.TypeName,
91+
ExtCode: strconv.Itoa(opts.ExtCode),
92+
PackageName: opts.PackageName,
93+
Imports: opts.Imports,
94+
CustomMarshalFunc: opts.CustomMarshalFunc,
95+
CustomUnmarshalFunc: opts.CustomUnmarshalFunc,
4596
})
4697
if err != nil {
4798
return nil, fmt.Errorf("failed to generateByType: %w", err)

cmd/gentypes/generator/type_gen.go.tpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func (o {{.Name}}) UnwrapOrElse(defaultValue func() {{.Type}}) {{.Type}} {
153153
}
154154

155155
func (o {{.Name}}) encodeValue(encoder *msgpack.Encoder) error {
156-
value, err := o.value.MarshalMsgpack()
156+
value, err := {{ .CustomMarshalFunc }}
157157
if err != nil {
158158
return err
159159
}
@@ -199,7 +199,7 @@ func (o *{{.Name}}) decodeValue(decoder *msgpack.Decoder) error {
199199
return o.newDecodeError(err)
200200
}
201201

202-
if err := o.value.UnmarshalMsgpack(a); err != nil {
202+
if err := {{ .CustomUnmarshalFunc }}; err != nil {
203203
return o.newDecodeError(err)
204204
}
205205

0 commit comments

Comments
 (0)