go generate を使える自動生成系ライブラリを触ってみる

それぞれ、 `go generate` のbefore <-> afterを載せていく
 
## Stringer

```
$ go get golang.org/x/tools/cmd/stringer
```

**before**

```go
package gen

//go:generate stringer -type=Pill
type Pill int

const (  
    Placebo Pill = iota
    Aspirin
    Ibuprofen
    Paracetamol
)
```

**after**

```go
// Code generated by "stringer -type=Pill"; DO NOT EDIT.

package gen

import "strconv"

const _Pill_name = "PlaceboAspirinIbuprofenParacetamol"

var _Pill_index = [...]uint8{0, 7, 14, 23, 34}

func (i Pill) String() string {
	if i < 0 || i >= Pill(len(_Pill_index)-1) {
		return "Pill(" + strconv.FormatInt(int64(i), 10) + ")"
	}
	return _Pill_name[_Pill_index[i]:_Pill_index[i+1]]
}
```

一応試す

```
package main

import (
	"fmt"

	"github.com/smith-30/gopg/gen"
)

func main() {
	fmt.Printf("%d\n", gen.Placebo)
	fmt.Println(gen.Placebo)
}
```

**結果**

```
0
Placebo
```

## jsonenums

```
$ go get github.com/campoy/jsonenums
```

**before**

```go

package gen

//go:generate jsonenums -type=Pill
//go:generate stringer -type=Pill
type Pill int

const (
	Placebo Pill = iota
	Aspirin
	Ibuprofen
	Paracetamol
)
```

**after**

```go

// generated by jsonenums -type=Pill; DO NOT EDIT

package gen

import (
	"encoding/json"
	"fmt"
)

var (
	_PillNameToValue = map[string]Pill{
		"Placebo":     Placebo,
		"Aspirin":     Aspirin,
		"Ibuprofen":   Ibuprofen,
		"Paracetamol": Paracetamol,
	}

	_PillValueToName = map[Pill]string{
		Placebo:     "Placebo",
		Aspirin:     "Aspirin",
		Ibuprofen:   "Ibuprofen",
		Paracetamol: "Paracetamol",
	}
)

func init() {
	var v Pill
	if _, ok := interface{}(v).(fmt.Stringer); ok {
		_PillNameToValue = map[string]Pill{
			interface{}(Placebo).(fmt.Stringer).String():     Placebo,
			interface{}(Aspirin).(fmt.Stringer).String():     Aspirin,
			interface{}(Ibuprofen).(fmt.Stringer).String():   Ibuprofen,
			interface{}(Paracetamol).(fmt.Stringer).String(): Paracetamol,
		}
	}
}

// MarshalJSON is generated so Pill satisfies json.Marshaler.
func (r Pill) MarshalJSON() ([]byte, error) {
	if s, ok := interface{}(r).(fmt.Stringer); ok {
		return json.Marshal(s.String())
	}
	s, ok := _PillValueToName[r]
	if !ok {
		return nil, fmt.Errorf("invalid Pill: %d", r)
	}
	return json.Marshal(s)
}

// UnmarshalJSON is generated so Pill satisfies json.Unmarshaler.
func (r *Pill) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return fmt.Errorf("Pill should be a string, got %s", data)
	}
	v, ok := _PillNameToValue[s]
	if !ok {
		return fmt.Errorf("invalid Pill %q", s)
	}
	*r = v
	return nil
}
```

試す

```
package main

import (
	"encoding/json"
	"fmt"

	"github.com/smith-30/gopg/gen"
)

func main() {
	s := gen.Sample{
		P: gen.Placebo,
	}
	b, _ := json.Marshal(s)
	fmt.Printf("%s\n", b)
}
```

```
{"P":"Placebo"}
```

いいですね、楽。

## enumer

```
$ go get github.com/alvaroloes/enumer
```

最後は上記２つをまとめてやってくれるようなやつです

**before**

```go

package gen

//go:generate enumer -type=Pill -json
type Pill int

const (
	Placebo Pill = iota
	Aspirin
	Ibuprofen
	Paracetamol
)
```

**after**

```go

// Code generated by "enumer -type=Pill -json"; DO NOT EDIT.

package gen

import (
	"encoding/json"
	"fmt"
)

const _PillName = "PlaceboAspirinIbuprofenParacetamol"

var _PillIndex = [...]uint8{0, 7, 14, 23, 34}

func (i Pill) String() string {
	if i < 0 || i >= Pill(len(_PillIndex)-1) {
		return fmt.Sprintf("Pill(%d)", i)
	}
	return _PillName[_PillIndex[i]:_PillIndex[i+1]]
}

var _PillValues = []Pill{0, 1, 2, 3}

var _PillNameToValueMap = map[string]Pill{
	_PillName[0:7]:   0,
	_PillName[7:14]:  1,
	_PillName[14:23]: 2,
	_PillName[23:34]: 3,
}

// PillString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func PillString(s string) (Pill, error) {
	if val, ok := _PillNameToValueMap[s]; ok {
		return val, nil
	}
	return 0, fmt.Errorf("%s does not belong to Pill values", s)
}

// PillValues returns all values of the enum
func PillValues() []Pill {
	return _PillValues
}

// IsAPill returns "true" if the value is listed in the enum definition. "false" otherwise
func (i Pill) IsAPill() bool {
	for _, v := range _PillValues {
		if i == v {
			return true
		}
	}
	return false
}

// MarshalJSON implements the json.Marshaler interface for Pill
func (i Pill) MarshalJSON() ([]byte, error) {
	return json.Marshal(i.String())
}

// UnmarshalJSON implements the json.Unmarshaler interface for Pill
func (i *Pill) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return fmt.Errorf("Pill should be a string, got %s", data)
	}

	var err error
	*i, err = PillString(s)
	return err
}

```

stringやjson系の他にこんなのが追加されています。
IsA~()は書いてません

```go

package main

import (
	"fmt"

	"github.com/smith-30/gopg/gen"
)

func main() {
	fmt.Printf("%#v\n", gen.PillValues())
	p, _ := gen.PillString("Placebo")
	fmt.Printf("%#v\n", p)
	_, err := gen.PillString("hoge")
	fmt.Printf("%#v\n", err)
}
```

```
[]gen.Pill{0, 1, 2, 3}
0
&errors.errorString{s:"hoge does not belong to Pill values"}
```

## mock

```
go get github.com/golang/mock
```

**before**

```go

package gen

//go:generate mockgen -source=repo.go -destination=./mock_gen/mock_repo.go
type Repository interface {
	Delete() error
}

type SampleRepository struct {
}

func (a *SampleRepository) Delete() error {
	return nil
}

```

**after**

```go

// Code generated by MockGen. DO NOT EDIT.
// Source: repo.go

// Package mock_gen is a generated GoMock package.
package mock_gen

import (
	gomock "github.com/golang/mock/gomock"
	reflect "reflect"
)

// MockRepository is a mock of Repository interface
type MockRepository struct {
	ctrl     *gomock.Controller
	recorder *MockRepositoryMockRecorder
}

// MockRepositoryMockRecorder is the mock recorder for MockRepository
type MockRepositoryMockRecorder struct {
	mock *MockRepository
}

// NewMockRepository creates a new mock instance
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
	mock := &MockRepository{ctrl: ctrl}
	mock.recorder = &MockRepositoryMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
	return m.recorder
}

// Delete mocks base method
func (m *MockRepository) Delete() error {
	ret := m.ctrl.Call(m, "Delete")
	ret0, _ := ret[0].(error)
	return ret0
}

// Delete indicates an expected call of Delete
func (mr *MockRepositoryMockRecorder) Delete() *gomock.Call {
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete))
}

```

試す
テストでこんな感じに使えます

```go
package gen

import (
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/smith-30/gopg/gen/mock_gen"
)

// Deleteが一回だけ呼ばれるか
// Returnに値をセットすれば振る舞いも変えられる
func TestSampleRepository_Delete(t *testing.T) {
	ctrl := gomock.NewController(t)
	mock := mock_gen.NewMockRepository(ctrl)

	mock.
		EXPECT().
		Delete().
		Return(nil).
		Times(1)

	mock.Delete()
	mock.Delete()
}

```


## まとめ

こういうgenerate系は、後から定義を追加しても
追加したことによる副作用を開発者があまり考えなくて済むのがよいですよね
積極的に使っていきたい