それぞれ、 go generate
のbefore <-> afterを載せていく
Stringer
$ go get golang.org/x/tools/cmd/stringer
before
package gen
//go:generate stringer -type=Pill
type Pill int
const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
)
after
// 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
package gen
//go:generate jsonenums -type=Pill
//go:generate stringer -type=Pill
type Pill int
const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
)
after
// 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
最後は上記2つをまとめてやってくれるようなやつです
before
package gen
//go:generate enumer -type=Pill -json
type Pill int
const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
)
after
// 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~()は書いてません
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
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
// 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))
}
試す
テストでこんな感じに使えます
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系は、後から定義を追加しても
追加したことによる副作用を開発者があまり考えなくて済むのがよいですよね
積極的に使っていきたい