diff --git a/emulator_test.go b/emulator_test.go index 3d0927d..d988051 100644 --- a/emulator_test.go +++ b/emulator_test.go @@ -3,6 +3,7 @@ package main import ( + "fmt" "os" "os/exec" "path/filepath" @@ -26,40 +27,49 @@ func TestGenerator(t *testing.T) { t.Fatalf("failed to getwd: %+v", err) } - t.Run("int64", func(t *testing.T) { + t.Run("int64", func(tr *testing.T) { if err := os.Chdir(filepath.Join(root, "testfiles/a")); err != nil { - t.Fatalf("chdir failed: %+v", err) + tr.Fatalf("chdir failed: %+v", err) } + // t.Logだと通常テスト時に出力されない & verboseモードでもt.Logだと改行されてしまう + // 以上により `fmt.Print` を採用 + fmt.Print("Failure pattern -> ") if err := run("Task"); err != nil { - t.Fatalf("failed to generate for testfiles/a: %+v", err) + tr.Fatalf("failed to generate for testfiles/a: %+v", err) } - execTest(t) + if err := run("Name"); err != nil { + tr.Fatalf("failed to generate for testfiles/a: %+v", err) + } + + execTest(tr) }) - t.Run("string", func(t *testing.T) { + t.Run("string", func(tr *testing.T) { + tr.Parallel() if err := os.Chdir(filepath.Join(root, "testfiles/b")); err != nil { - t.Fatalf("chdir failed: %+v", err) + tr.Fatalf("chdir failed: %+v", err) } if err := run("Task"); err != nil { - t.Fatalf("failed to generate for testfiles/a: %+v", err) + tr.Fatalf("failed to generate for testfiles/b: %+v", err) } - execTest(t) + execTest(tr) }) - t.Run("datastore.Key", func(t *testing.T) { + t.Run("datastore.Key", func(tr *testing.T) { + tr.Parallel() if err := os.Chdir(filepath.Join(root, "testfiles/c")); err != nil { - t.Fatalf("chdir failed: %+v", err) + tr.Fatalf("chdir failed: %+v", err) } if err := run("Task"); err != nil { - t.Fatalf("failed to generate for testfiles/a: %+v", err) + tr.Fatalf("failed to generate for testfiles/c: %+v", err) } - execTest(t) + execTest(tr) }) } diff --git a/generator.go b/generator.go index 996d0b9..a799a60 100644 --- a/generator.go +++ b/generator.go @@ -3,28 +3,96 @@ package main import ( "io" "log" + "strings" "text/template" + + "github.com/iancoleman/strcase" ) +type IndexesInfo struct { + Comment string + ConstName string + Label string + Method string +} + +type FieldInfo struct { + DsTag string + Field string + FieldType string + Indexes []*IndexesInfo +} + +type ImportInfo struct { + Name string +} + type generator struct { PackageName string + ImportName string + ImportList []ImportInfo GeneratedFileName string FileName string StructName string - GoGenerate string RepositoryStructName string RepositoryInterfaceName string KeyFieldName string KeyFieldType string - KeyValueName string // lower camel case + + FieldInfos []*FieldInfo + + EnableIndexes bool + FieldInfoForIndexes *FieldInfo + BoolCriteriaCnt int + SliceExist bool +} + +func (g *generator) setting() { + g.RepositoryInterfaceName = g.StructName + "Repository" + g.RepositoryStructName = strcase.ToLowerCamel(g.RepositoryInterfaceName) + g.buildConditions() +} + +func (g *generator) buildConditions() { + for _, field := range g.FieldInfos { + switch field.FieldType { + case "time.Time": + g.ImportList = append(g.ImportList, ImportInfo{"time"}) + } + } } func (g *generator) generate(writer io.Writer) { g.setting() - t := template.Must(template.New("tmpl").Parse(tmpl)) + funcMap := template.FuncMap{ + "Parse": func(fieldType string) string { + if strings.HasPrefix(fieldType, "[]") { + fieldType = fieldType[2:] + } + fn := "Int" + switch fieldType { + case typeInt: + case typeInt64: + fn = "Int64" + case typeFloat64: + fn = "Float64" + case typeString: + fn = "String" + case typeBool: + fn = "Bool" + default: + panic("invalid types") + } + return fn + }, + "HasPrefixSlice": func(types string) bool { + return strings.HasPrefix(types, "[]") + }, + } + t := template.Must(template.New("tmpl").Funcs(funcMap).Parse(tmpl)) err := t.Execute(writer, g) @@ -33,154 +101,454 @@ func (g *generator) generate(writer io.Writer) { } } -func (g *generator) setting() { - g.GoGenerate = "go:generate" - g.RepositoryInterfaceName = g.StructName + "Repository" - g.setRepositoryStructName() +func (g *generator) generateLabel(writer io.Writer) { + t := template.Must(template.New("tmplLabel").Parse(tmplLabel)) + + err := t.Execute(writer, g) + + if err != nil { + log.Printf("failed to execute template: %+v", err) + } +} + +func (g *generator) generateConstant(writer io.Writer) { + t := template.Must(template.New("tmplConst").Parse(tmplConst)) + + err := t.Execute(writer, g) + + if err != nil { + log.Printf("failed to execute template: %+v", err) + } +} + +const tmplConst = `// THIS FILE IS A GENERATED CODE. DO NOT EDIT +package {{ .PackageName }} + +import ( + "log" + "strconv" +) + +type BoolCriteria string + +const ( + BoolCriteriaEmpty BoolCriteria = "" + BoolCriteriaTrue BoolCriteria = "true" + BoolCriteriaFalse BoolCriteria = "false" +) + +func (src BoolCriteria) Bool() bool { + return src == BoolCriteriaTrue } -func (g *generator) setRepositoryStructName() { - name := g.RepositoryInterfaceName - prefix := name[:1] - r := []rune(prefix)[0] - if 65 <= r && r <= 90 { - prefix = string(r + 32) +type NumericCriteria string + +const ( + NumericCriteriaEmpty NumericCriteria = "" + NumericCriteriaBase NumericCriteria = "0" +) + +func (str NumericCriteria) Parse(i interface{}) NumericCriteria { + switch x := i.(type) { + case int: + return NumericCriteria(strconv.Itoa(x)) + case int64: + return NumericCriteria(strconv.FormatInt(x, 10)) + case float64: + return NumericCriteria(strconv.FormatFloat(x, 'f', -1, 64)) + default: + log.Println("invalid NumericCriteria value") } - g.RepositoryStructName = prefix + name[1:] + return NumericCriteriaEmpty } +func (str NumericCriteria) Int() int { + i32, err := strconv.Atoi(string(str)) + if err != nil { + return -1 + } + return i32 +} + +func (str NumericCriteria) Int64() int64 { + i64, err := strconv.ParseInt(string(str), 10, 64) + if err != nil { + return -1 + } + return i64 +} + +func (str NumericCriteria) Float64() float64 { + f64, err := strconv.ParseFloat(string(str), 64) + if err != nil { + return -1 + } + return f64 +} +` + +const tmplLabel = `// THIS FILE IS A GENERATED CODE. EDIT OK +package {{ .PackageName }} + +const ( +{{- range $fi := .FieldInfos }} + {{- range $idx := $fi.Indexes }} + // {{ $idx.Comment }}検索用ラベル + {{ $idx.ConstName }} = "{{ $idx.Label }}" + {{- end }} +{{- end }} +) +` + // nolint:lll const tmpl = `// THIS FILE IS A GENERATED CODE. DO NOT EDIT -package {{.PackageName}} +package {{ .PackageName }} import ( "context" +{{- range .ImportList }} + "{{ .Name }}" +{{- end }} "cloud.google.com/go/datastore" +{{- if and (eq .SliceExist true) (eq .EnableIndexes false) }} + "github.com/go-utils/dedupe" +{{- end }} +{{- if eq .KeyFieldType "*datastore.Key" }} + "github.com/google/uuid" +{{- end }} +{{- if eq .EnableIndexes true }} + "github.com/knightso/xian" +{{- end }} "golang.org/x/xerrors" ) -//{{.GoGenerate}} mockgen -source {{.GeneratedFileName}}.go -destination mock_{{.GeneratedFileName}}/mock_{{.GeneratedFileName}}.go +{{ $generate := "go:generate" }} +//{{ $generate }} mockgen -source {{ .GeneratedFileName }}.go -destination mock_{{ .GeneratedFileName }}/mock_{{ .GeneratedFileName }}.go -type {{.RepositoryInterfaceName}} interface { +type {{ .RepositoryInterfaceName }} interface { // Single - Get(ctx context.Context, {{.KeyValueName}} {{.KeyFieldType}}) (*{{.StructName}}, error) - Insert(ctx context.Context, subject *{{.StructName}}) ({{.KeyFieldType}}, error) - Update(ctx context.Context, subject *{{.StructName}}) error - Delete(ctx context.Context, subject *{{.StructName}}) error - DeleteBy{{.KeyFieldName}}(ctx context.Context, {{.KeyValueName}} {{.KeyFieldType}}) error + Get(ctx context.Context, {{ .KeyValueName }} {{ .KeyFieldType }}) (*{{ .StructName }}, error) + Insert(ctx context.Context, subject *{{ .StructName }}) ({{ .KeyFieldType }}, error) + Update(ctx context.Context, subject *{{ .StructName }}) error + Delete(ctx context.Context, subject *{{ .StructName }}) error + DeleteBy{{ .KeyFieldName }}(ctx context.Context, {{ .KeyValueName }} {{ .KeyFieldType }}) error // Multiple - GetMulti(ctx context.Context, {{.KeyValueName}}s []{{.KeyFieldType}}) ([]*{{.StructName}}, error) - InsertMulti(ctx context.Context, subjects []*{{.StructName}}) ([]{{.KeyFieldType}}, error) - UpdateMulti(ctx context.Context, subjects []*{{.StructName}}) error - DeleteMulti(ctx context.Context, subjects []*{{.StructName}}) error - DeleteMultiBy{{.KeyFieldName}}s(ctx context.Context, {{.KeyValueName}}s []{{.KeyFieldType}}) error + GetMulti(ctx context.Context, {{ .KeyValueName }}s []{{ .KeyFieldType }}) ([]*{{ .StructName }}, error) + InsertMulti(ctx context.Context, subjects []*{{ .StructName }}) ([]{{ .KeyFieldType }}, error) + UpdateMulti(ctx context.Context, subjects []*{{ .StructName }}) error + DeleteMulti(ctx context.Context, subjects []*{{ .StructName }}) error + DeleteMultiBy{{ .KeyFieldName }}s(ctx context.Context, {{ .KeyValueName }}s []{{ .KeyFieldType }}) error + // List + List(ctx context.Context, req *{{ .StructName }}ListReq, q *datastore.Query) ([]*{{ .StructName }}, error) + // misc + GetKindName() string } -type {{.RepositoryStructName}} struct { +type {{ .RepositoryStructName }} struct { kind string datastoreClient *datastore.Client } -func New{{.RepositoryInterfaceName}}(datastoreClient *datastore.Client) {{.RepositoryInterfaceName}} { - return &{{.RepositoryStructName}}{ - kind: "{{.StructName}}", +// New{{ .RepositoryInterfaceName }} constructor +func New{{ .RepositoryInterfaceName }}(datastoreClient *datastore.Client) {{ .RepositoryInterfaceName }} { + return &{{ .RepositoryStructName }}{ + kind: "{{ .StructName }}", datastoreClient: datastoreClient, } } -func (repo *{{.RepositoryStructName}}) getKeys(subjects ...*{{.StructName}}) ([]*datastore.Key, error) { +// GetKindName KindName getter +func (repo *{{ .RepositoryStructName }}) GetKindName() string { + return repo.kind +} + +// getKeys Entityからkeyを取得する +func (repo *{{ .RepositoryStructName }}) getKeys(subjects ...*{{ .StructName }}) ([]*datastore.Key, error) { keys := make([]*datastore.Key, 0, len(subjects)) for _, subject := range subjects { - key := subject.{{.KeyFieldName}} -{{- if eq .KeyFieldType "int64"}} + key := subject.{{ .KeyFieldName }} +{{- if eq .KeyFieldType "int64" }} if key == 0 { return nil, xerrors.New("ID must be set") } keys = append(keys, datastore.IDKey(repo.kind, key, nil)) -{{- else if eq .KeyFieldType "string"}} +{{- else if eq .KeyFieldType "string" }} if key == "" { return nil, xerrors.New("ID must be set") } keys = append(keys, datastore.NameKey(repo.kind, key, nil)) -{{- else}} +{{- else }} if key == nil { - key = datastore.IncompleteKey(repo.kind, nil) + key = datastore.NameKey(repo.kind, uuid.New().String(), nil) } keys = append(keys, key) -{{- end}} +{{- end }} } return keys, nil } -func (repo *{{.RepositoryStructName}}) Get(ctx context.Context, {{.KeyValueName}} {{.KeyFieldType}}) (*{{.StructName}}, error) { -{{- if eq .KeyFieldType "int64"}} - key := datastore.IDKey(repo.kind, {{.KeyValueName}}, nil) -{{else if eq .KeyFieldType "string"}} - key := datastore.NameKey(repo.kind, {{.KeyValueName}}, nil) -{{else}} - key := {{.KeyValueName}} -{{end}} - subject := new({{.StructName}}) +{{- if eq .EnableIndexes true }} +// saveIndexes 拡張フィルタを保存する +func (repo *{{ .RepositoryStructName }}) saveIndexes(subjects ...*{{ .StructName }}) error { + for _, subject := range subjects { + idx := xian.NewIndexes({{ .StructName }}IndexesConfig) +{{- range $fi := .FieldInfos }} +{{- $PrefixIsSlice := HasPrefixSlice $fi.FieldType}} +{{- range $idx := $fi.Indexes }} +{{- if or (eq $fi.FieldType "bool") (eq $fi.FieldType "int" ) (eq $fi.FieldType "int64" ) (eq $fi.FieldType "float64" ) }} + idx.{{ $idx.Method }}({{ $idx.ConstName }}, subject.{{ $fi.Field }}) +{{- else if eq $fi.FieldType "string" }} +{{- if eq $idx.Method "AddPrefix" }} + idx.{{ $idx.Method }}es({{ $idx.ConstName }}, subject.{{ $fi.Field }}) +{{- else }} + idx.{{ $idx.Method }}({{ $idx.ConstName }}, subject.{{ $fi.Field }}) +{{- end }} +{{- else if eq $fi.FieldType "time.Time" }} + idx.{{ $idx.Method }}({{ $idx.ConstName }}, subject.{{ $fi.Field }}.Unix()) +{{- else if eq $PrefixIsSlice true }} + idx.{{ $idx.Method }}({{ $idx.ConstName }}, subject.{{ $fi.Field }}) +{{- end }} +{{- end }} +{{- end }} + built, err := idx.Build() + if err != nil { + return err + } + subject.Indexes = built + } + + return nil +} + +// {{ .StructName }}IndexesConfig {{ .StructName }}用のIndexesConfigを設定する +var {{ .StructName }}IndexesConfig = &xian.Config{ + // IgnoreCase Case insensitive + // └──大文字小文字を区別しない + IgnoreCase: true, + // SaveNoFiltersIndex 検索時にフィルタを設定しない場合、この拡張フィルタなし検索用インデックスのEquality Filterが自動で適用される + // ├── falseで、拡張フィルタのあり・なしの両パターンの検索がある場合、カスタムインデックスを両パターン分用意しておく必要がある + // └── trueにしておくことでカスタムインデックスを半分に節約することができる + SaveNoFiltersIndex: true, +} +{{- end }} + +// {{ .StructName }}ListReq List取得時に渡すリクエスト +// └─ bool/int(64)|float64 は stringの独自型で渡す(BoolCriteria | NumericCriteria) +type {{ .StructName }}ListReq struct { +{{- range .FieldInfos }} +{{- if eq .FieldType "bool" }} + {{ .Field }} BoolCriteria +{{- else if or (eq .FieldType "int") (eq .FieldType "int64") (eq .FieldType "float64" ) }} + {{ .Field }} NumericCriteria +{{- else }} + {{ .Field }} {{ .FieldType }} +{{- end }} +{{- end }} +} + +// List datastore.Queryを使用し条件抽出をする +// └─ 第3引数はNOT/OR/IN/RANGEなど、より複雑な条件を適用したいときにつける +// └─ 基本的にnilを渡せば良い +// BUG(54mch4n) 潜在的なバグがあるかもしれない +func (repo *{{ .RepositoryStructName }}) List(ctx context.Context, req *{{ .StructName }}ListReq, q *datastore.Query) ([]*{{ .StructName }}, error) { + if q == nil { + q = datastore.NewQuery(repo.kind) + } +{{ $Enable := .EnableIndexes }} +{{- if eq $Enable true }} + filters := xian.NewFilters({{ .StructName }}IndexesConfig) +{{- end }} +{{- range $fi := .FieldInfos }} +{{- $PrefixIsSlice := HasPrefixSlice $fi.FieldType}} +{{- if eq $fi.FieldType "bool" }} + if req.{{ $fi.Field }} != "" { +{{- if eq $Enable true }} +{{- range $idx := $fi.Indexes }} + filters.{{ $idx.Method }}({{ $idx.ConstName }}, req.{{ $fi.Field }}) +{{- end }} +{{- else }} + q = q.Filter("{{ $fi.DsTag }} =", req.{{ $fi.Field }}.Bool()) +{{- end }} + } +{{- else if eq $fi.FieldType "string" }} + if req.{{ $fi.Field }} != "" { +{{- if eq $Enable true }} +{{- range $idx := $fi.Indexes }} + filters.{{ $idx.Method }}({{ $idx.ConstName }}, req.{{ $fi.Field }}) +{{- end }} +{{- else }} + q = q.Filter("{{ $fi.DsTag }} =", req.{{ $fi.Field }}) +{{- end }} + } +{{- else if or (eq $fi.FieldType "int") (eq $fi.FieldType "int64") (eq $fi.FieldType "float64" ) }} + if req.{{ $fi.Field }} != NumericCriteriaEmpty { +{{- if eq $Enable true }} +{{- range $idx := $fi.Indexes }} + filters.{{ $idx.Method }}({{ $idx.ConstName }}, req.{{ $fi.Field }}.{{ Parse $fi.FieldType }}()) +{{- end }} +{{- else }} + q = q.Filter("{{ $fi.DsTag }} =", req.{{ $fi.Field }}.{{ Parse $fi.FieldType }}()) +{{- end }} + } +{{- else if eq $fi.FieldType "time.Time" }} + if !req.{{ $fi.Field }}.IsZero() { +{{- if eq $Enable true }} +{{- range $idx := $fi.Indexes }} + filters.{{ $idx.Method }}({{ $idx.ConstName }}, req.{{ $fi.Field }}.Unix()) +{{- end }} +{{- else }} + q = q.Filter("{{ $fi.DsTag }} =", req.{{ $fi.Field }}) +{{- end }} + } +{{- else if eq $PrefixIsSlice true }} + if len(req.{{ $fi.Field }}) > 0 { +{{- if eq $Enable true }} +{{- range $idx := $fi.Indexes }} + filters.{{ $idx.Method }}({{ $idx.ConstName }}, req.{{ $fi.Field }}) +{{- end }} +{{- else }} + dedupe.Do(&req.{{ $fi.Field }}) + for _, x := range req.{{ $fi.Field }} { + q = q.Filter("{{ $fi.DsTag }} =", x) + } +{{- end }} + } +{{- end }} +{{- end }} +{{ if eq $Enable true }} + built, err := filters.Build() + if err != nil { + return nil, err + } + + for _, f := range built { + q = q.Filter("{{ .FieldInfoForIndexes.DsTag }} =", f) + } +{{- end }} + subjects := make([]*{{ .StructName }}, 0) + keys, err := repo.datastoreClient.GetAll(ctx, q, &subjects) + if err != nil { + return nil, err + } + + for i, k := range keys { + if k != nil { +{{- if eq .KeyFieldType "int64" }} + subjects[i].ID = k.ID +{{- else if eq .KeyFieldType "string" }} + subjects[i].ID = k.Name +{{- else }} + subjects[i].ID = k +{{- end }} + } + } + + return subjects, nil +} + +// Get 処理中の {{ .StructName }} の取得処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) Get(ctx context.Context, {{ .KeyValueName }} {{ .KeyFieldType }}) (*{{ .StructName }}, error) { +{{- if eq .KeyFieldType "int64" }} + key := datastore.IDKey(repo.kind, {{ .KeyValueName }}, nil) +{{ else if eq .KeyFieldType "string" }} + key := datastore.NameKey(repo.kind, {{ .KeyValueName }}, nil) +{{ else }} + key := {{ .KeyValueName }} +{{ end }} + subject := new({{ .StructName }}) err := repo.datastoreClient.Get(ctx, key, subject) if err != nil { return nil, err } -{{if eq .KeyFieldType "int64"}} - subject.{{.KeyFieldName}} = key.ID -{{else if eq .KeyFieldType "string"}} - subject.{{.KeyFieldName}} = key.Name -{{else}} - subject.{{.KeyFieldName}} = key -{{end}} +{{ if eq .KeyFieldType "int64" }} + subject.{{ .KeyFieldName }} = key.ID +{{ else if eq .KeyFieldType "string" }} + subject.{{ .KeyFieldName }} = key.Name +{{ else }} + subject.{{ .KeyFieldName }} = key +{{ end }} return subject, nil } -func (repo *{{.RepositoryStructName}}) Insert(ctx context.Context, subject *{{.StructName}}) ({{.KeyFieldType}}, error) { -{{- if eq .KeyFieldType "int64"}} +// Insert 処理中の {{ .StructName }} の登録処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) Insert(ctx context.Context, subject *{{ .StructName }}) ({{ .KeyFieldType }}, error) { +{{- if eq .KeyFieldType "int64" }} zero := int64(0) -{{else if eq .KeyFieldType "string"}} +{{ else if eq .KeyFieldType "string" }} zero := "" -{{else}} - var zero {{.KeyFieldType}} -{{end}} +{{ else }} + var zero {{ .KeyFieldType }} +{{ end }} keys, err := repo.getKeys(subject) if err != nil { return zero, xerrors.Errorf("error in getKeys method: %w", err) } - key, err := repo.datastoreClient.Put(ctx, keys[0], subject) + _, err = repo.datastoreClient.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + if err := tx.Get(keys[0], nil); err != datastore.ErrNoSuchEntity { + return xerrors.Errorf("error in datastore.Transaction.Get method: %w", err) + } + +{{- if eq .EnableIndexes true }} + if err := repo.saveIndexes(subject); err != nil { + return xerrors.Errorf("error in saveIndexes method: %w", err) + } +{{- end }} + + _, err := tx.Put(keys[0], subject) + if err != nil { + return err + } + + return nil + }) if err != nil { - return zero, err + return zero, xerrors.Errorf("error in datastore.Client.RunInTransaction method: %w", err) } -{{if eq .KeyFieldType "int64"}} - return key.ID, nil -{{else if eq .KeyFieldType "string"}} - return key.Name, nil -{{else}} - return key, nil -{{end -}} +{{ if eq .KeyFieldType "int64" }} + return keys[0].ID, nil +{{- else if eq .KeyFieldType "string" }} + return keys[0].Name, nil +{{- else }} + return keys[0], nil +{{- end }} } -func (repo *{{.RepositoryStructName}}) Update(ctx context.Context, subject *{{.StructName}}) error { - if _, err := repo.Get(ctx, subject.{{.KeyFieldName}}); err == datastore.ErrNoSuchEntity { - return err - } +// Update 処理中の {{ .StructName }} の更新処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) Update(ctx context.Context, subject *{{ .StructName }}) (err error) { + _, err = repo.datastoreClient.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + keys, err := repo.getKeys(subject) + if err != nil { + return xerrors.Errorf("error in getKeys method: %w", err) + } - keys, err := repo.getKeys(subject) - if err != nil { - return xerrors.Errorf("error in getKeys method: %w", err) - } + if err := tx.Get(keys[0], nil); err == datastore.ErrNoSuchEntity { + return xerrors.Errorf("error in datastore.Transaction.Get method: %w", err) + } + +{{- if eq .EnableIndexes true }} + if err := repo.saveIndexes(subject); err != nil { + return xerrors.Errorf("error in saveIndexes method: %w", err) + } +{{- end }} + + if _, err := tx.Put(keys[0], subject); err != nil { + return err + } - if _, err := repo.datastoreClient.Put(ctx, keys[0], subject); err != nil { - return err + return nil + }) + if err != nil { + err = xerrors.Errorf("error in datastore.Client.RunInTransaction method: %w", err) } - return nil + return } -func (repo *{{.RepositoryStructName}}) Delete(ctx context.Context, subject *{{.StructName}}) error { +// Delete 処理中の {{ .StructName }} の削除処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) Delete(ctx context.Context, subject *{{ .StructName }}) error { keys, err := repo.getKeys(subject) if err != nil { return xerrors.Errorf("error in getKeys method: %w", err) @@ -189,114 +557,142 @@ func (repo *{{.RepositoryStructName}}) Delete(ctx context.Context, subject *{{.S return repo.datastoreClient.Delete(ctx, keys[0]) } -func (repo *{{.RepositoryStructName}}) DeleteBy{{.KeyFieldName}}(ctx context.Context, {{.KeyValueName}} {{.KeyFieldType}}) error { -{{- if eq .KeyFieldType "int64"}} - key := datastore.IDKey(repo.kind, {{.KeyValueName}}, nil) -{{- else if eq .KeyFieldType "string"}} - key := datastore.NameKey(repo.kind, {{.KeyValueName}}, nil) -{{- else}} - key := {{.KeyValueName}} -{{end}} +// DeleteBy{{ .KeyFieldName }} 処理中の {{ .StructName }} の{{ .KeyFieldName }}から削除処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) DeleteBy{{ .KeyFieldName }}(ctx context.Context, {{ .KeyValueName }} {{ .KeyFieldType }}) error { +{{- if eq .KeyFieldType "int64" }} + key := datastore.IDKey(repo.kind, {{ .KeyValueName }}, nil) +{{- else if eq .KeyFieldType "string" }} + key := datastore.NameKey(repo.kind, {{ .KeyValueName }}, nil) +{{- else }} + key := {{ .KeyValueName }} +{{ end }} return repo.datastoreClient.Delete(ctx, key) } -func (repo *{{.RepositoryStructName}}) GetMulti(ctx context.Context, {{.KeyValueName}}s []{{.KeyFieldType}}) ([]*{{.StructName}}, error) { -{{- if eq .KeyFieldType "int64"}} - keys := make([]*datastore.Key, 0, len({{.KeyValueName}}s)) +// GetMulti 処理中の {{ .StructName }} の一括取得処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) GetMulti(ctx context.Context, {{ .KeyValueName }}s []{{ .KeyFieldType }}) (subjects []*{{ .StructName }}, err error) { +{{- if eq .KeyFieldType "int64" }} + keys := make([]*datastore.Key, 0, len({{ .KeyValueName }}s)) - for i := range {{.KeyValueName}}s { - keys = append(keys, datastore.IDKey(repo.kind, {{.KeyValueName}}s[i], nil)) + for i := range {{ .KeyValueName }}s { + keys = append(keys, datastore.IDKey(repo.kind, {{ .KeyValueName }}s[i], nil)) } -{{else if eq .KeyFieldType "string"}} - keys := make([]*datastore.Key, 0, len({{.KeyValueName}}s)) +{{ else if eq .KeyFieldType "string" }} + keys := make([]*datastore.Key, 0, len({{ .KeyValueName }}s)) - for i := range {{.KeyValueName}}s { - keys = append(keys, datastore.NameKey(repo.kind, {{.KeyValueName}}s[i], nil)) + for i := range {{ .KeyValueName }}s { + keys = append(keys, datastore.NameKey(repo.kind, {{ .KeyValueName }}s[i], nil)) } -{{else}} - keys := {{.KeyValueName}}s -{{end}} - vessels := make([]*{{.StructName}}, len({{.KeyValueName}}s)) - err := repo.datastoreClient.GetMulti(ctx, keys, vessels) - - for i := range vessels { - if vessels[i] != nil { -{{- if eq .KeyFieldType "int64"}} - vessels[i].{{.KeyFieldName}} = keys[i].ID -{{- else if eq .KeyFieldType "string"}} - vessels[i].{{.KeyFieldName}} = keys[i].Name -{{- else}} - vessels[i].{{.KeyFieldName}} = keys[i] -{{- end}} +{{ else }} + keys := {{ .KeyValueName }}s +{{ end }} + subjects = make([]*{{ .StructName }}, len({{ .KeyValueName }}s)) + err = repo.datastoreClient.GetMulti(ctx, keys, subjects) + + for i := range subjects { + if subjects[i] != nil { +{{- if eq .KeyFieldType "int64" }} + subjects[i].{{ .KeyFieldName }} = keys[i].ID +{{- else if eq .KeyFieldType "string" }} + subjects[i].{{ .KeyFieldName }} = keys[i].Name +{{- else }} + subjects[i].{{ .KeyFieldName }} = keys[i] +{{- end }} } } - return vessels, err + return } -func (repo *{{.RepositoryStructName}}) InsertMulti(ctx context.Context, subjects []*{{.StructName}}) ([]{{.KeyFieldType}}, error) { +// InsertMulti 処理中の {{ .StructName }} の一括挿入処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) InsertMulti(ctx context.Context, subjects []*{{ .StructName }}) ({{ .KeyValueName }}s []{{ .KeyFieldType }}, err error) { keys, err := repo.getKeys(subjects...) if err != nil { return nil, xerrors.Errorf("error in getKeys method: %w", err) } - var cnt int - if err := repo.datastoreClient.GetMulti(ctx, keys, make([]*{{.StructName}}, 0, len(subjects))); err != nil { - if errs, ok := err.(datastore.MultiError); ok { - for _, err := range errs { - if err == datastore.ErrNoSuchEntity { - cnt++ + _, err = repo.datastoreClient.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + var cnt int + if err := tx.GetMulti(keys, make([]*{{ .StructName }}, len(subjects))); err != nil { + if errs, ok := err.(datastore.MultiError); ok { + for _, err := range errs { + if err == datastore.ErrNoSuchEntity { + cnt++ + } else { + return xerrors.Errorf("error in datastore.Client.GetMulti method: %w", err) + } } } } - } + if len(subjects) != cnt { + return xerrors.Errorf("already exist. (%d)", len(subjects)-cnt) + } +{{ if eq .EnableIndexes true }} + if err := repo.saveIndexes(subjects...); err != nil { + return xerrors.Errorf("error in saveIndexes method: %w", err) + } +{{- end }} + _, err := tx.PutMulti(keys, subjects) + if err != nil { + return err + } - if len(subjects) != cnt { - return nil, xerrors.Errorf("already exist. (%d)", len(subjects)-cnt) - } + {{ .KeyValueName }}s = make([]{{ .KeyFieldType }}, len(keys)) + for i := range keys { + if keys[i] != nil { +{{- if eq .KeyFieldType "int64" }} + {{ .KeyValueName }}s[i] = keys[i].ID +{{- else if eq .KeyFieldType "string" }} + {{ .KeyValueName }}s[i] = keys[i].Name +{{- else }} + {{ .KeyValueName }}s[i] = keys[i] +{{- end }} + } + } - resKeys, err := repo.datastoreClient.PutMulti(ctx, keys, subjects) + return nil + }) if err != nil { - return nil, err - } - - vessels := make([]{{.KeyFieldType}}, 0, len(resKeys)) - for i := range resKeys { - if keys[i] != nil { -{{- if eq .KeyFieldType "int64"}} - vessels[i] = resKeys[i].ID -{{- else if eq .KeyFieldType "string"}} - vessels[i] = resKeys[i].Name -{{- else}} - vessels[i] = resKeys[i] -{{- end}} - } + return nil, xerrors.Errorf("error in datastore.Client.RunInTransaction method: %w", err) } - return vessels, err + return } -func (repo *{{.RepositoryStructName}}) UpdateMulti(ctx context.Context, subjects []*{{.StructName}}) error { +// UpdateMulti 処理中の {{ .StructName }} の一括更新処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) UpdateMulti(ctx context.Context, subjects []*{{ .StructName }}) error { keys, err := repo.getKeys(subjects...) if err != nil { return xerrors.Errorf("error in getKeys method: %w", err) } - if err := repo.datastoreClient.GetMulti(ctx, keys, make([]*{{.StructName}}, 0, len(subjects))); err != nil { - if _, ok := err.(datastore.MultiError); ok { + _, err = repo.datastoreClient.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + if err := tx.GetMulti(keys, make([]*{{ .StructName }}, len(subjects))); err != nil { + if _, ok := err.(datastore.MultiError); ok { + return err + } + } +{{ if eq .EnableIndexes true }} + if err := repo.saveIndexes(subjects...); err != nil { + return xerrors.Errorf("error in saveIndexes method: %w", err) + } +{{ end }} + _, err = tx.PutMulti(keys, subjects) + if err != nil { return err } - } - _, err = repo.datastoreClient.PutMulti(ctx, keys, subjects) + return nil + }) if err != nil { - return err + return xerrors.Errorf("error in datastore.Client.RunInTransaction method: %w", err) } return nil } -func (repo *{{.RepositoryStructName}}) DeleteMulti(ctx context.Context, subjects []*{{.StructName}}) error { +// DeleteMulti 処理中の {{ .StructName }} の一括削除処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) DeleteMulti(ctx context.Context, subjects []*{{ .StructName }}) error { keys, err := repo.getKeys(subjects...) if err != nil { return xerrors.Errorf("error in getKeys method: %w", err) @@ -305,22 +701,23 @@ func (repo *{{.RepositoryStructName}}) DeleteMulti(ctx context.Context, subjects return repo.datastoreClient.DeleteMulti(ctx, keys) } -func (repo *{{.RepositoryStructName}}) DeleteMultiBy{{.KeyFieldName}}s(ctx context.Context, {{.KeyValueName}}s []{{.KeyFieldType}}) error { -{{- if eq .KeyFieldType "int64"}} - keys := make([]*datastore.Key, 0, len({{.KeyValueName}}s)) +// DeleteMultiBy{{ .KeyFieldName }}s 処理中の {{ .StructName }} の{{ .KeyFieldName }}群を元に一括削除処理一切の責任を持ち、これを行う +func (repo *{{ .RepositoryStructName }}) DeleteMultiBy{{ .KeyFieldName }}s(ctx context.Context, {{ .KeyValueName }}s []{{ .KeyFieldType }}) error { +{{- if eq .KeyFieldType "int64" }} + keys := make([]*datastore.Key, 0, len({{ .KeyValueName }}s)) - for i := range {{.KeyValueName}}s { - keys = append(keys, datastore.IDKey(repo.kind, {{.KeyValueName}}s[i], nil)) + for i := range {{ .KeyValueName }}s { + keys = append(keys, datastore.IDKey(repo.kind, {{ .KeyValueName }}s[i], nil)) } -{{else if eq .KeyFieldType "string"}} - keys := make([]*datastore.Key, 0, len({{.KeyValueName}}s)) +{{ else if eq .KeyFieldType "string" }} + keys := make([]*datastore.Key, 0, len({{ .KeyValueName }}s)) - for i := range {{.KeyValueName}}s { - keys = append(keys, datastore.NameKey(repo.kind, {{.KeyValueName}}s[i], nil)) + for i := range {{ .KeyValueName }}s { + keys = append(keys, datastore.NameKey(repo.kind, {{ .KeyValueName }}s[i], nil)) } -{{else}} - keys := {{.KeyValueName}}s -{{end}} +{{ else }} + keys := {{ .KeyValueName }}s +{{ end }} return repo.datastoreClient.DeleteMulti(ctx, keys) } ` diff --git a/go.mod b/go.mod index bc47618..626f579 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,24 @@ module github.com/go-generalize/repo_generator -go 1.14 +go 1.12 require ( + cloud.google.com/go v0.57.0 // indirect cloud.google.com/go/bigquery v1.6.0 // indirect cloud.google.com/go/datastore v1.1.0 github.com/fatih/structtag v1.2.0 + github.com/go-utils/cont v0.1.1 + github.com/go-utils/dedupe v0.1.3 github.com/golang/mock v1.4.3 github.com/golang/protobuf v1.4.1 // indirect + github.com/google/uuid v1.1.1 github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 - golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 // indirect - golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect - golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 + github.com/knightso/xian v0.1.0 + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/net v0.0.0-20200513185701-a91f0712d120 // indirect + golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c // indirect + golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 - google.golang.org/api v0.23.0 // indirect - google.golang.org/appengine v1.6.6 // indirect - google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 // indirect - google.golang.org/grpc v1.29.1 // indirect + google.golang.org/api v0.24.0 // indirect + google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 // indirect ) diff --git a/go.sum b/go.sum index e07099c..b24bf3f 100644 --- a/go.sum +++ b/go.sum @@ -6,12 +6,12 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0 h1:GGslhk/BU052LPlnI1vpp3fcbUs+hQ3E+Doti/3/vF8= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0 h1:WRz29PgAsVEyPSDHyk+0fpEkwEFyfhHn+JbksT6gIL4= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -40,6 +40,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -50,6 +51,10 @@ github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-utils/cont v0.1.1 h1:my04uGn+7M3e7yx6TtABznKSA/zGoAy2J2pTUxqA9hg= +github.com/go-utils/cont v0.1.1/go.mod h1:zXQ9kh3mb8Bydc0Ce+12tHwn/cb/+zL8twUeOs4E+eA= +github.com/go-utils/dedupe v0.1.3 h1:VMB9L7Xws6jk3Srxadix6EVKY292HBKE7cV8GqtX36k= +github.com/go-utils/dedupe v0.1.3/go.mod h1:djuV2+bSo4GJof/CgXcV54k5NwDkSqnevZ5ySgPp/hM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -57,7 +62,6 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18h github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= @@ -66,7 +70,6 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= @@ -74,7 +77,6 @@ github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= @@ -92,7 +94,10 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -105,14 +110,23 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knightso/xian v0.1.0 h1:tbSxtKEB0jMK9M4U6ftbh33LwLDASQqknR0pTodRNBQ= +github.com/knightso/xian v0.1.0/go.mod h1:awKIZpjAL+4BI/y7lanh65Qf2uz2lwQGK8mK+gW+QUo= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -123,7 +137,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -133,7 +146,6 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd h1:zkO/Lhoka23X63N9OSzpSeROEUQ5ODw47tM3YWjygbs= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= @@ -146,7 +158,6 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -171,15 +182,14 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -209,15 +219,15 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w= -golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c h1:kISX68E8gSkNYAFRFiDU8rl5RIn1sJYKYb/r2vMLDrU= +golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -253,7 +263,6 @@ golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56 h1:DFtSed2q3HtNuVazwVDZ4nSRS/JrZEig0gz2BY4VNrg= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -261,8 +270,9 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200409170454-77362c5149f0/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 h1:BMFHd4OFnFtWX46Xj4DN6vvT1btiBxyq+s0orYBqcQY= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97 h1:DAuln/hGp+aJiHpID1Y1hYzMEPP5WLwtZHPb50mN0OE= +golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= @@ -274,19 +284,18 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0 h1:0q95w+VuFtv4PAx4PZVQdBMmYbaCHbnfKaEiDIcVyag= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.23.0 h1:YlvGEOq2NA2my8cZ/9V8BcEO9okD48FlJcdqN0xJL3s= -google.golang.org/api v0.23.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0 h1:cG03eaksBzhfSIk7JRGctfp3lanklcOM/mTGvow7BbQ= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -305,7 +314,6 @@ google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce h1:1mbrb1tUU+Zmt5C94IGKADBTJZjZXAd+BubWi7r9EiI= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -313,8 +321,9 @@ google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 h1:pSLkPbrjnPyLDYUO2VM9mDLqo2V6CFBY84lFSZAfoi4= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 h1:fiNLklpBwWK1mth30Hlwk+fcdBmIALlgF5iy77O37Ig= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -322,7 +331,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= @@ -332,19 +340,19 @@ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLY google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= diff --git a/main.go b/main.go index f9919c6..49d5049 100644 --- a/main.go +++ b/main.go @@ -11,9 +11,17 @@ import ( "strings" "github.com/fatih/structtag" + "github.com/go-utils/cont" "github.com/iancoleman/strcase" + "golang.org/x/xerrors" ) +func init() { + for _, x := range supportType { + supportType = append(supportType, "[]"+x) + } +} + func main() { l := len(os.Args) if l < 2 { @@ -84,70 +92,229 @@ func traverse(pkg *ast.Package, fs *token.FileSet, structName string) error { } } - return fmt.Errorf("no such struct: %s", structName) + return xerrors.Errorf("no such struct: %s", structName) } func generate(gen *generator, fs *token.FileSet, structType *ast.StructType) error { + dupMap := make(map[string]int) + fieldLabel = gen.StructName + queryLabel for _, field := range structType.Fields.List { // structの各fieldを調査 + if len(field.Names) != 1 { + return xerrors.New("`field.Names` must have only one element") + } + name := field.Names[0].Name - if field.Tag == nil { + pos := fs.Position(field.Pos()).String() + + typeName := getTypeName(field.Type) + if !cont.Contains(supportType, typeName) { + log.Printf( + "%s: the type of `%s` is an invalid type in struct `%s` [%s]\n", + pos, name, gen.StructName, typeName, + ) continue } - pos := fs.Position(field.Pos()).String() + if strings.HasPrefix(typeName, "[]") { + gen.SliceExist = true + } - tags, err := structtag.Parse(strings.Trim(field.Tag.Value, "`")) + if field.Tag == nil { + fieldInfo := &FieldInfo{ + DsTag: name, + Field: name, + FieldType: typeName, + Indexes: make([]*IndexesInfo, 0), + } + appendIndexesInfo(fieldInfo, dupMap) + gen.FieldInfos = append(gen.FieldInfos, fieldInfo) + continue + } - if err != nil { + if tags, err := structtag.Parse(strings.Trim(field.Tag.Value, "`")); err != nil { log.Printf( "%s: tag for %s in struct %s in %s", - pos, field.Names[0].Name, gen.StructName, gen.GeneratedFileName+".go", + pos, name, gen.StructName, gen.GeneratedFileName+".go", ) - continue + } else { + if name == "Indexes" { + gen.EnableIndexes = true + fieldInfo := &FieldInfo{ + DsTag: name, + Field: name, + FieldType: typeName, + } + if tag, err := dataStoreTagCheck(pos, tags); err != nil { + return xerrors.Errorf("error in tagCheck method: %w", err) + } else if tag != "" { + fieldInfo.DsTag = tag + } + gen.FieldInfoForIndexes = fieldInfo + continue + } + if _, err := tags.Get("datastore_key"); err != nil { + fieldInfo := &FieldInfo{ + DsTag: name, + Field: name, + FieldType: typeName, + Indexes: make([]*IndexesInfo, 0), + } + if fieldInfo, err = appendIndexer(pos, tags, fieldInfo, dupMap); err != nil { + return xerrors.Errorf("error in appendIndexer: %w", err) + } + gen.FieldInfos = append(gen.FieldInfos, fieldInfo) + continue + } + if err := keyFieldHandler(gen, tags, name, typeName, pos); err != nil { + return xerrors.Errorf("error in keyFieldHandler: %w", err) + } } + } - _, err = tags.Get("datastore_key") - + { + fp, err := os.Create(gen.GeneratedFileName + ".go") if err != nil { - continue + panic(err) } + defer fp.Close() - dsTag, err := tags.Get("datastore") + gen.generate(fp) + } - // datastore タグが存在しないか-になっていない - if err != nil || strings.Split(dsTag.Value(), ",")[0] != "-" { - return fmt.Errorf("%s: key field for datastore should have datastore:\"-\" tag", pos) + if gen.EnableIndexes { + path := gen.FileName + "_label.go" + fp, err := os.Create(path) + if err != nil { + panic(err) } + defer fp.Close() + gen.generateLabel(fp) + } - if len(field.Names) != 1 { - return fmt.Errorf("%s: datastore_key tag can be set to only one field", pos) + { + fp, err := os.Create("constant.go") + if err != nil { + panic(err) } + defer fp.Close() + gen.generateConstant(fp) + } - gen.KeyFieldName = field.Names[0].Name - gen.KeyFieldType = getTypeName(field.Type) + return nil +} - if gen.KeyFieldType != "int64" && - gen.KeyFieldType != "string" && - !strings.HasSuffix(gen.KeyFieldType, ".Key") { - return fmt.Errorf("%s: supported key types are int64, string, *datastore.Key", pos) - } +func keyFieldHandler(gen *generator, tags *structtag.Tags, name, typeName, pos string) error { + dsTag, err := tags.Get("datastore") - gen.KeyValueName = strcase.ToLowerCamel(field.Names[0].Name) + // datastore タグが存在しないか-になっていない + if err != nil || strings.Split(dsTag.Value(), ",")[0] != "-" { + return xerrors.Errorf("%s: key field for datastore should have datastore:\"-\" tag", pos) } - fp, err := os.Create(gen.GeneratedFileName + ".go") + gen.KeyFieldName = name + gen.KeyFieldType = typeName - if err != nil { - panic(err) + if gen.KeyFieldType != typeInt64 && + gen.KeyFieldType != typeString && + !strings.HasSuffix(gen.KeyFieldType, ".Key") { + return xerrors.Errorf("%s: supported key types are int64, string, *datastore.Key", pos) + } + + gen.KeyValueName = strcase.ToLowerCamel(name) + return nil +} + +func appendIndexer(pos string, tags *structtag.Tags, fieldInfo *FieldInfo, dupMap map[string]int) (*FieldInfo, error) { + if tag, err := dataStoreTagCheck(pos, tags); err != nil { + return nil, xerrors.Errorf("error in tagCheck method: %w", err) + } else if tag != "" { + fieldInfo.DsTag = tag + } + if idr, err := tags.Get("indexer"); err != nil || fieldInfo.FieldType != typeString { + appendIndexesInfo(fieldInfo, dupMap) + } else { + filters := strings.Split(idr.Value(), ",") + dupIdr := make(map[string]struct{}) + for _, fil := range filters { + idx := &IndexesInfo{ + ConstName: fieldLabel + fieldInfo.Field, + Label: uppercaseExtraction(fieldInfo.Field, dupMap), + Method: "Add", + } + var dupFlag string + switch fil { + case "p", "prefix": // 前方一致 (AddPrefix) + idx.Method += prefix + idx.ConstName += prefix + idx.Comment = fmt.Sprintf("%s %s前方一致", idx.ConstName, fieldInfo.Field) + dupFlag = "p" + case "s", "suffix": /* TODO 後方一致 + idx.Method += Suffix + idx.ConstName += Suffix + idx.Comment = fmt.Sprintf("%s %s後方一致", idx.ConstName, name) + dup = "s"*/ + case "e", "equal": // 完全一致 (Add) Default + idx.Comment = fmt.Sprintf("%s %s", idx.ConstName, fieldInfo.Field) + dupIdr["equal"] = struct{}{} + dupFlag = "e" + case "l", "like": // 部分一致 + idx.Method += biunigrams + idx.ConstName += "Like" + idx.Comment = fmt.Sprintf("%s %s部分一致", idx.ConstName, fieldInfo.Field) + dupFlag = "l" + default: + continue + } + if _, ok := dupIdr[dupFlag]; ok { + continue + } + dupIdr[dupFlag] = struct{}{} + fieldInfo.Indexes = append(fieldInfo.Indexes, idx) + } } + return fieldInfo, nil +} - gen.generate( - fp, - ) +func uppercaseExtraction(name string, dupMap map[string]int) (lower string) { + for _, x := range name { + if 65 <= x && x <= 90 { + lower += string(x + 32) + } + } + if _, ok := dupMap[lower]; !ok { + dupMap[lower] = 1 + } else { + dupMap[lower]++ + lower = fmt.Sprintf("%s%d", lower, dupMap[lower]) + } + return +} - fp.Close() +func appendIndexesInfo(fieldInfo *FieldInfo, dupMap map[string]int) { + idx := &IndexesInfo{ + ConstName: fieldLabel + fieldInfo.Field, + Label: uppercaseExtraction(fieldInfo.Field, dupMap), + Method: "Add", + } + idx.Comment = fmt.Sprintf("%s %s", idx.ConstName, fieldInfo.Field) + if fieldInfo.FieldType != typeString { + idx.Method += "Something" + } + fieldInfo.Indexes = append(fieldInfo.Indexes, idx) +} - return nil +func dataStoreTagCheck(pos string, tags *structtag.Tags) (string, error) { + if dsTag, err := tags.Get("datastore"); err == nil { + tag := strings.Split(dsTag.Value(), ",")[0] + if !valueCheck.MatchString(tag) { + return "", xerrors.Errorf("%s: key field for datastore should have other than blanks and symbols tag", pos) + } + if strings.Contains("0123456789", string(tag[0])) { + return "", xerrors.Errorf("%s: key field for datastore should have prefix other than numbers required", pos) + } + return tag, nil + } + return "", nil } diff --git a/misc.go b/misc.go index 755650a..3a927cf 100644 --- a/misc.go +++ b/misc.go @@ -2,6 +2,34 @@ package main import ( "go/ast" + "regexp" +) + +const ( + biunigrams = "Biunigrams" + prefix = "Prefix" + queryLabel = "QueryLabel" + typeString = "string" + typeInt = "int" + typeInt64 = "int64" + typeFloat64 = "float64" + typeBool = "bool" + typeTime = "time.Time" + datastoreKey = "*datastore.Key" +) + +var ( + fieldLabel string + valueCheck = regexp.MustCompile("^[0-9a-zA-Z_]+$") + supportType = []string{ + typeBool, + typeString, + typeInt, + typeInt64, + typeFloat64, + typeTime, + datastoreKey, + } ) func getTypeName(typ ast.Expr) string { @@ -10,12 +38,14 @@ func getTypeName(typ ast.Expr) string { return getTypeName(v.X) + "." + v.Sel.Name case *ast.Ident: - return v.Name case *ast.StarExpr: return "*" + getTypeName(v.X) + case *ast.ArrayType: + return "[]" + getTypeName(v.Elt) + default: return "" } diff --git a/testfiles/.gitignore b/testfiles/.gitignore index 529f624..96a9eeb 100644 --- a/testfiles/.gitignore +++ b/testfiles/.gitignore @@ -1,2 +1,4 @@ +mock_*/ *_gen.go -mock_*/ \ No newline at end of file +*_label.go +constant.go \ No newline at end of file diff --git a/testfiles/a/name.go b/testfiles/a/name.go new file mode 100644 index 0000000..7326fd4 --- /dev/null +++ b/testfiles/a/name.go @@ -0,0 +1,22 @@ +package task + +import ( + "time" +) + +//go:generate repo_generator Name +//go:generate gofmt -w ./ + +// Name 拡張インデックスあり +type Name struct { + ID int64 `datastore:"-" datastore_key:""` // supported type: string, int64, *datastore.Key + Created time.Time `datastore:"created"` + // supported indexer tags word: e/equal(Default), l/like, p/prefix, + // TODO s/suffix + Desc string `datastore:"description" indexer:"l"` + Desc2 string `datastore:"description2" indexer:"p"` + Done bool `datastore:"done"` + Count int `datastore:"count"` + PriceList []int `datastore:"priceList"` + Indexes []string `datastore:"indexes"` +} diff --git a/testfiles/a/task.go b/testfiles/a/task.go index fe53f38..ad77c33 100644 --- a/testfiles/a/task.go +++ b/testfiles/a/task.go @@ -5,10 +5,20 @@ import ( ) //go:generate repo_generator Task +//go:generate gofmt -w ./ +// Task 拡張インデックスなし type Task struct { - Desc string `datastore:"description"` - Created time.Time `datastore:"created"` - Done bool `datastore:"done"` - ID int64 `datastore:"-" datastore_key:""` // supported type: string, int64, *datastore.Key + ID int64 `datastore:"-" datastore_key:""` // supported type: string, int64, *datastore.Key + Desc string `datastore:"description"` + Created time.Time `datastore:"created"` + Done bool `datastore:"done"` + Done2 bool `datastore:"done2"` + Count int `datastore:"count"` + Count64 int64 `datastore:"count64"` + NameList []string `datastore:"nameList"` + Proportion float64 `datastore:"proportion"` + Flag Flag `datastore:"flag"` // NG } + +type Flag bool diff --git a/testfiles/a/tests/task_test.go b/testfiles/a/tests/task_test.go index 5d5ed7b..c8cce82 100644 --- a/testfiles/a/tests/task_test.go +++ b/testfiles/a/tests/task_test.go @@ -4,6 +4,7 @@ package tests import ( "context" + "fmt" "os" "testing" "time" @@ -40,7 +41,7 @@ func compareTask(t *testing.T, expected, actual *task.Task) { t.Fatalf("unexpected id: %d(expected: %d)", actual.ID, expected.ID) } - if actual.Created != expected.Created { + if !actual.Created.Equal(expected.Created) { t.Fatalf("unexpected time: %s(expected: %s)", actual.Created, expected.Created) } @@ -53,6 +54,363 @@ func compareTask(t *testing.T, expected, actual *task.Task) { } } +func TestDatastoreTransactionTask(t *testing.T) { + client := initDatastoreClient(t) + + taskRepo := task.NewTaskRepository(client) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + var ids []int64 + defer func() { + defer cancel() + if err := taskRepo.DeleteMultiByIDs(ctx, ids); err != nil { + t.Fatal(err) + } + }() + + now := time.Unix(0, time.Now().UnixNano()) + desc := "Hello, World!" + + t.Run("Multi", func(tr *testing.T) { + tks := make([]*task.Task, 0) + for i := int64(1); i <= 10; i++ { + tk := &task.Task{ + ID: i * 100, + Desc: fmt.Sprintf("%s%d", desc, i), + Created: now, + Done: true, + Done2: false, + Count: int(i), + Count64: 0, + Proportion: 0.12345 + float64(i), + Flag: task.Flag(true), + NameList: []string{"a", "b", "c"}, + } + tks = append(tks, tk) + } + idList, err := taskRepo.InsertMulti(ctx, tks) + if err != nil { + tr.Fatalf("%+v", err) + } + ids = append(ids, idList...) + + tks2 := make([]*task.Task, 0) + for i := int64(1); i <= 10; i++ { + tk := &task.Task{ + ID: i * 100, + Desc: fmt.Sprintf("%s%d", desc, i), + Created: now, + Done: true, + Done2: false, + Count: int(i), + Count64: i, + Proportion: 0.12345 + float64(i), + Flag: task.Flag(true), + NameList: []string{"a", "b", "c"}, + } + tks2 = append(tks2, tk) + } + if err := taskRepo.UpdateMulti(ctx, tks2); err != nil { + tr.Fatalf("%+v", err) + } + + if tks[0].ID != tks2[0].ID { + tr.Fatalf("unexpected id: %d (expected: %d)", tks[0].ID, tks2[0].ID) + } + }) + + t.Run("Single", func(tr *testing.T) { + tk := &task.Task{ + ID: 1001, + Desc: fmt.Sprintf("%s%d", desc, 1001), + Created: now, + Done: true, + Done2: false, + Count: 11, + Count64: 11, + Proportion: 0.12345 + 11, + NameList: []string{"a", "b", "c"}, + } + id, err := taskRepo.Insert(ctx, tk) + if err != nil { + tr.Fatalf("%+v", err) + } + ids = append(ids, id) + + tk.Count = 12 + if err := taskRepo.Update(ctx, tk); err != nil { + tr.Fatalf("%+v", err) + } + + tsk, err := taskRepo.Get(ctx, tk.ID) + if err != nil { + tr.Fatalf("%+v", err) + } + + if tsk.Count != 12 { + tr.Fatalf("unexpected Count: %d (expected: %d)", tsk.Count, 12) + } + }) +} + +func TestDatastoreListTask(t *testing.T) { + client := initDatastoreClient(t) + + taskRepo := task.NewTaskRepository(client) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + var ids []int64 + defer func() { + defer cancel() + if err := taskRepo.DeleteMultiByIDs(ctx, ids); err != nil { + t.Fatal(err) + } + }() + + now := time.Unix(0, time.Now().UnixNano()) + desc := "Hello, World!" + + tks := make([]*task.Task, 0) + for i := int64(1); i <= 10; i++ { + tk := &task.Task{ + ID: i * 100, + Desc: fmt.Sprintf("%s%d", desc, i), + Created: now, + Done: true, + Done2: false, + Count: int(i), + Count64: 0, + Proportion: 0.12345 + float64(i), + Flag: task.Flag(true), + NameList: []string{"a", "b", "c"}, + } + tks = append(tks, tk) + } + ids, err := taskRepo.InsertMulti(ctx, tks) + if err != nil { + t.Fatalf("%+v", err) + } + + t.Run("int(1件)", func(t *testing.T) { + req := &task.TaskListReq{ + Count: task.NumericCriteriaBase.Parse(1), + } + + tasks, err := taskRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 1 { + t.Fatal("not match") + } + }) + + t.Run("float(1件)", func(t *testing.T) { + req := &task.TaskListReq{ + Proportion: task.NumericCriteriaBase.Parse(1.12345), + } + + tasks, err := taskRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 1 { + t.Fatal("not match") + } + }) + + t.Run("bool(10件)", func(t *testing.T) { + req := &task.TaskListReq{ + Done: task.BoolCriteriaTrue, + } + + tasks, err := taskRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 10 { + t.Fatal("not match") + } + }) + + t.Run("time.Time(10件)", func(t *testing.T) { + req := &task.TaskListReq{ + Created: now, + } + + tasks, err := taskRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 10 { + t.Fatal("not match") + } + }) + + t.Run("[]string(10件)", func(t *testing.T) { + req := &task.TaskListReq{ + NameList: []string{"a", "b"}, + } + + tasks, err := taskRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 10 { + t.Fatal("not match") + } + }) +} + +func TestDatastoreListNameWithIndexes(t *testing.T) { + client := initDatastoreClient(t) + + nameRepo := task.NewNameRepository(client) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + var ids []int64 + defer func() { + defer cancel() + if err := nameRepo.DeleteMultiByIDs(ctx, ids); err != nil { + t.Fatal(err) + } + }() + + now := time.Unix(0, time.Now().UnixNano()) + desc := "Hello, World!" + desc2 := "Prefix, Test!" + + tks := make([]*task.Name, 0) + for i := int64(1); i <= 10; i++ { + tk := &task.Name{ + ID: i, + Created: now, + Desc: fmt.Sprintf("%s%d", desc, i), + Desc2: fmt.Sprintf("%s%d", desc2, i), + Done: true, + Count: int(i), + PriceList: []int{1, 2, 3, 4, 5}, + } + tks = append(tks, tk) + } + ids, err := nameRepo.InsertMulti(ctx, tks) + if err != nil { + t.Fatalf("%+v", err) + } + + t.Run("int(1件)", func(t *testing.T) { + req := &task.NameListReq{ + Count: task.NumericCriteriaBase.Parse(1), + } + + tasks, err := nameRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 1 { + t.Fatal("not match") + } + }) + + t.Run("bool(10件)", func(t *testing.T) { + req := &task.NameListReq{ + Done: task.BoolCriteriaTrue, + } + + tasks, err := nameRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 10 { + t.Fatal("not match") + } + }) + + t.Run("like(10件)", func(t *testing.T) { + req := &task.NameListReq{ + Desc: "ll", + } + + tasks, err := nameRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 10 { + t.Fatal("not match") + } + }) + + t.Run("prefix", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { + req := &task.NameListReq{ + Desc2: "Pre", + } + + tasks, err := nameRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 10 { + t.Fatal("not match") + } + }) + + t.Run("Failure", func(t *testing.T) { + req := &task.NameListReq{ + Desc2: "He", + } + + tasks, err := nameRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 0 { + t.Fatal("not match") + } + }) + }) + + t.Run("time.Time(10件)", func(t *testing.T) { + req := &task.NameListReq{ + Created: now, + } + + tasks, err := nameRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 10 { + t.Fatal("not match") + } + }) + + t.Run("[]int(10件)", func(t *testing.T) { + req := &task.NameListReq{ + PriceList: []int{1, 2, 3}, + } + + tasks, err := nameRepo.List(ctx, req, nil) + if err != nil { + t.Fatalf("%+v", err) + } + + if len(tasks) != 10 { + t.Fatal("not match") + } + }) +} + func TestDatastore(t *testing.T) { client := initDatastoreClient(t) diff --git a/testfiles/b/task.go b/testfiles/b/task.go index a3393d8..0fcdb3e 100644 --- a/testfiles/b/task.go +++ b/testfiles/b/task.go @@ -5,6 +5,7 @@ import ( ) //go:generate repo_generator Task +//go:generate gofmt -w ./ type Task struct { Desc string `datastore:"description"` diff --git a/testfiles/b/tests/task_test.go b/testfiles/b/tests/task_test.go index df5dc23..24cd547 100644 --- a/testfiles/b/tests/task_test.go +++ b/testfiles/b/tests/task_test.go @@ -40,7 +40,7 @@ func compareTask(t *testing.T, expected, actual *task.Task) { t.Fatalf("unexpected id: %s(expected: %s)", actual.ID, expected.ID) } - if actual.Created != expected.Created { + if !actual.Created.Equal(expected.Created) { t.Fatalf("unexpected time: %s(expected: %s)", actual.Created, expected.Created) } diff --git a/testfiles/c/task.go b/testfiles/c/task.go index b3715ce..fd152e0 100644 --- a/testfiles/c/task.go +++ b/testfiles/c/task.go @@ -7,6 +7,7 @@ import ( ) //go:generate repo_generator Task +//go:generate gofmt -w ./ type Task struct { Desc string `datastore:"description"` diff --git a/testfiles/c/tests/task_test.go b/testfiles/c/tests/task_test.go index 6b3c904..46d82db 100644 --- a/testfiles/c/tests/task_test.go +++ b/testfiles/c/tests/task_test.go @@ -10,6 +10,7 @@ import ( "cloud.google.com/go/datastore" task "github.com/go-generalize/repo_generator/testfiles/c" + "github.com/google/uuid" ) func initDatastoreClient(t *testing.T) *datastore.Client { @@ -40,7 +41,7 @@ func compareTask(t *testing.T, expected, actual *task.Task) { t.Fatalf("unexpected id: %s(expected: %s)", actual.ID, expected.ID) } - if actual.Created != expected.Created { + if !actual.Created.Equal(expected.Created) { t.Fatalf("unexpected time: %s(expected: %s)", actual.Created, expected.Created) } @@ -64,10 +65,8 @@ func TestDatastore(t *testing.T) { now := time.Unix(time.Now().Unix(), 0) desc := "hello" - incmplKey := datastore.IncompleteKey("Task", nil) - id, err := taskRepo.Insert(ctx, &task.Task{ - ID: incmplKey, + ID: datastore.NameKey("Task", uuid.New().String(), nil), Desc: desc, Created: now, Done: true,