Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ linters:
- "golang.org/x/tools"
- "github.com/vmihailenco/msgpack/v5"
- "github.com/tarantool/go-option"
- "github.com/google/uuid"
test:
files:
- "$test"
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.

### Added

- gentypes: Add support for third-party types.

### Changed

### Fixed
Expand Down
65 changes: 60 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,65 @@ Or you can use it to generate file from go:

Flags:

• `-package`: Path to the Go package containing types to wrap (default: `"."`)
• `-ext-code`: MessagePack extension code to use for custom types (must be between
-128 and 127, no default value)
• `-verbose`: Enable verbose output (default: `false`)
* `-package`: Path to the Go package containing types to wrap (default: `"."`)
* `-ext-code`: MessagePack extension code to use for custom types (must be between
-128 and 127, no default value)
* `-verbose`: Enable verbose output (default: `false`)
* `-force`: Ignore absence of marshal/unmarshal methods on type (default: `false`).
Helpful for types from third-party modules.
* `-imports`: Add imports to generated file (default is empty).
Helpful for types from third-party modules.
* `-marshal-func`: func that should do marshaling (default is `MarshalMsgpack` method).
Helpful for types from third-party modules.
Should be func of type `func(v T) ([]byte, error)` and should
be located in the same dir or should be imported.
* `-unmarshal-func`: func that should do unmarshalling (default is `UnmarshalMsgpack` method).
Helpful for types from third-party modules.
Should be func of type `func(v *T, data []byte) error` and should
be located in the same dir or should be imported.

#### Generating Optional Types for Third-Party Modules

Sometimes you need to generate an optional type for a type from a third-party module,
and you can't add `MarshalMsgpack`/`UnmarshalMsgpack` methods to it.
In this case, you can use the `-force`, `-imports`, `-marshal-func`, and `-unmarshal-func` flags.

For example, to generate an optional type for `github.com/google/uuid.UUID`:

1. Create a file with marshal and unmarshal functions for the third-party type.
For example, `uuid.go`:

```go
package main

import (
"errors"

"github.com/google/uuid"
)

func encodeUUID(uuid uuid.UUID) ([]byte, error) {
return uuid[:], nil
}

var (
ErrInvalidLength = errors.New("invalid length")
)

func decodeUUID(uuid *uuid.UUID, data []byte) error {
if len(data) != len(uuid) {
return ErrInvalidLength
}
copy(uuid[:], data)
return nil
}
```

2. Use the following `go:generate` command:

```go
//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
```

### Using Generated Types

Expand Down Expand Up @@ -238,4 +293,4 @@ BSD 2-Clause License
[coverage-url]: https://coveralls.io/github/tarantool/go-option?branch=master
[telegram-badge]: https://img.shields.io/badge/Telegram-join%20chat-blue.svg
[telegram-en-url]: http://telegram.me/tarantool
[telegram-ru-url]: http://telegram.me/tarantoolru
[telegram-ru-url]: http://telegram.me/tarantoolru
34 changes: 34 additions & 0 deletions cmd/gentypes/flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"maps"
"slices"
"strings"
)

type stringListFlag []string

func (s *stringListFlag) String() string {
return strings.Join(*s, ", ")
}

func (s *stringListFlag) Set(s2 string) error {
*s = append(*s, s2)
return nil
}

func deleteDuplicates(s stringListFlag) stringListFlag {
uniqMap := map[string]struct{}{}
for _, val := range s {
uniqMap[val] = struct{}{}
}

return slices.Collect(maps.Keys(uniqMap))
}

func (s *stringListFlag) Get() []string {
deduped := deleteDuplicates(*s)
slices.Sort(deduped)

return deduped
}
5 changes: 4 additions & 1 deletion cmd/gentypes/generate.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
//go:generate go run github.com/tarantool/go-option/cmd/gentypes -ext-code 1 -package test FullMsgpackExtType
//go:generate go run github.com/tarantool/go-option/cmd/gentypes -ext-code 1 -package internal/test FullMsgpackExtType
//go:generate go run github.com/tarantool/go-option/cmd/gentypes -ext-code 2 -force -package internal/test HiddenTypeAlias
//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

package main
73 changes: 62 additions & 11 deletions cmd/gentypes/generator/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
_ "embed"
"fmt"
"strconv"
"strings"
"text/template"
)

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

const (
maxNameParts = 2
)

func constructTypeName(typeName string) string {
splittedName := strings.SplitN(typeName, ".", maxNameParts)
switch len(splittedName) {
case 1:
typeName = splittedName[0]
case maxNameParts:
typeName = splittedName[1]
default:
panic("invalid type name: " + typeName)
}

return "Optional" + typeName
}

// GenerateOptions is the options for the code generation.
type GenerateOptions struct {
// TypeName is the name of the type to generate optional to.
TypeName string
// ExtCode is the extension code.
ExtCode int
// PackageName is the name of the package to generate to.
PackageName string
// Imports is the list of imports to add to the generated code.
Imports []string
// CustomMarshalFunc is the name of the custom marshal function.
CustomMarshalFunc string
// CustomUnmarshalFunc is the name of the custom unmarshal function.
CustomUnmarshalFunc string
}

// GenerateByType generates the code for the optional type.
func GenerateByType(typeName string, code int, packageName string) ([]byte, error) {
func GenerateByType(opts GenerateOptions) ([]byte, error) {
var buf bytes.Buffer

if opts.CustomMarshalFunc == "" {
opts.CustomMarshalFunc = "o.value.MarshalMsgpack()"
} else {
opts.CustomMarshalFunc += "(o.value)"
}

if opts.CustomUnmarshalFunc == "" {
opts.CustomUnmarshalFunc = "o.value.UnmarshalMsgpack(a)"
} else {
opts.CustomUnmarshalFunc += "(&o.value, a)"
}

err := cTypeGenTemplate.Execute(&buf, struct {
Name string
Type string
ExtCode string
PackageName string
Imports []string
Name string
Type string
ExtCode string
PackageName string
Imports []string
CustomMarshalFunc string
CustomUnmarshalFunc string
}{
Name: "Optional" + typeName,
Type: typeName,
ExtCode: strconv.Itoa(code),
PackageName: packageName,
Imports: nil,
Name: constructTypeName(opts.TypeName),
Type: opts.TypeName,
ExtCode: strconv.Itoa(opts.ExtCode),
PackageName: opts.PackageName,
Imports: opts.Imports,
CustomMarshalFunc: opts.CustomMarshalFunc,
CustomUnmarshalFunc: opts.CustomUnmarshalFunc,
})
if err != nil {
return nil, fmt.Errorf("failed to generateByType: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions cmd/gentypes/generator/type_gen.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (o {{.Name}}) UnwrapOrElse(defaultValue func() {{.Type}}) {{.Type}} {
}

func (o {{.Name}}) encodeValue(encoder *msgpack.Encoder) error {
value, err := o.value.MarshalMsgpack()
value, err := {{ .CustomMarshalFunc }}
if err != nil {
return err
}
Expand Down Expand Up @@ -199,7 +199,7 @@ func (o *{{.Name}}) decodeValue(decoder *msgpack.Decoder) error {
return o.newDecodeError(err)
}

if err := o.value.UnmarshalMsgpack(a); err != nil {
if err := {{ .CustomUnmarshalFunc }}; err != nil {
return o.newDecodeError(err)
}

Expand Down
Loading