Compare commits

..

70 Commits

Author SHA1 Message Date
Kevin Wan
78ea0769fd feat: simplify httpc (#1748)
* feat: simplify httpc

* chore: fix lint errors

* chore: fix log url issue

* chore: fix log url issue

* refactor: handle resp & err in ResponseHandler

* chore: remove unnecessary var names in return clause
2022-04-03 14:32:27 +08:00
Kevin Wan
e0fa8d820d feat: return original value of setbit in redis (#1746) 2022-04-02 20:25:51 +08:00
Kevin Wan
dfd58c213c fix: model generation bug on with cache (#1743)
* fix: model generation bug on with cache

* chore: refine template

* chore: fix test failure
2022-04-02 15:36:06 +08:00
Kevin Wan
83cacf51b7 chore: update goctl version to 1.3.4 (#1742) 2022-04-02 14:19:34 +08:00
Kevin Wan
6dccfa29fd feat: let model customizable (#1738) 2022-04-01 22:19:52 +08:00
anqiansong
7e0b0ab0b1 Fix zrpc code generation error with --remote (#1739)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-01 22:19:33 +08:00
Kevin Wan
ac18cc470d chore: refactor to use const instead of var (#1731) 2022-04-01 15:23:45 +08:00
Fyn
f4471846ff feat(goctl): supports model code 'DO NOT EDIT' (#1728)
Resolves: #1710
2022-04-01 14:48:45 +08:00
anqiansong
9c2d526a11 Fix unit test (#1730)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-04-01 14:46:12 +08:00
Kevin Wan
2b9fc26c38 refactor: guard timeout on API files (#1726) 2022-03-31 21:39:02 +08:00
Xiaoju Jiang
321dc2d410 Added support for setting the parameter size accepted by the interface and custom timeout and maxbytes in API file (#1713)
* Added support for setting the parameter size accepted by the interface

* support custom timeout and maxbytes in API file

* support timeout used unit

* remove goctl maxBytes
2022-03-31 20:20:00 +08:00
Fyn
500bd87c85 fix(goctl): api format with reader input (#1722)
resolves #1721
2022-03-31 00:20:51 +08:00
Kevin Wan
e9620c8c05 chore: refactor code (#1708) 2022-03-24 22:10:15 +08:00
aimuz
70e51bb352 fix: empty slice are set to nil (#1702)
support for empty slce, Same behavior as json.Unmarshal
2022-03-24 21:41:38 +08:00
Kevin Wan
278cd123c8 feat: remove reentrance in redislock, timeout bug (#1704) 2022-03-24 16:17:01 +08:00
Kevin Wan
3febb1a5d0 chore: refactor code (#1700) 2022-03-23 19:09:45 +08:00
Mikael
d8054d8def fix -cache=true insert no clean cache (#1672)
* fix -cache=true insert no clean cache

* fix -cache=true insert no clean cache
2022-03-23 18:55:16 +08:00
Kevin Wan
ec271db7a0 chore: refactor code (#1699) 2022-03-23 18:24:44 +08:00
benqi
bbac994c8a feat: add getset command in redis and kv (#1693) 2022-03-23 18:02:56 +08:00
Kevin Wan
c1d9e6a00b feat: add httpc.Parse (#1698) 2022-03-23 17:58:21 +08:00
anqiansong
0aeb49a6b0 Add verbose flag (#1696)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-22 21:00:26 +08:00
Kevin Wan
fe262766b4 chore: fix lint issue (#1694) 2022-03-22 13:31:05 +08:00
Kevin Wan
7181505c8a Update LICENSE 2022-03-21 10:32:41 +08:00
Kevin Wan
f060a226bc refactor: simplify the code (#1670) 2022-03-20 17:26:12 +08:00
Mervin.Wong
93d524b797 fix: the new RawFieldNames considers the tag with options. (#1663)
Co-authored-by: JinfaWang <wangjinfa@iie.ac.cn>
2022-03-20 16:59:19 +08:00
anqiansong
5c169f4f49 Remove debug log (#1669)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-20 16:28:36 +08:00
Kevin Wan
d29dfa12e3 feat: support -base to specify base image for goctl docker (#1668)
* feat: support -base to specify base image for goctl docker

* chore: update usage
2022-03-20 11:17:55 +08:00
anqiansong
194f55e08e Remove unused code (#1667)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-19 23:15:11 +08:00
Kevin Wan
c0f9892fe3 feat: add Dockerfile for goctl (#1666) 2022-03-19 23:07:17 +08:00
anqiansong
227104d7d7 feat: Remove command goctl rpc proto (#1665)
* Fix goctl completion expression

* Fix code generation error if the pkg of pb/grpc is same to zrpc call client pkg

* Remove deprecated comment on action goctl rpc new

* Remove zrpc code generation on action goctl rpc proto

* Remove zrpc code generation on action goctl rpc proto

* Remove Generator interface

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-19 22:50:22 +08:00
anqiansong
448029aa4b Mkdir if not exists (#1659)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-17 21:44:22 +08:00
Fyn
17e0afeac0 fix(goctl): model method FindOneCtx should be FindOne (#1656) 2022-03-17 17:16:53 +08:00
ronething-bot
18916b5189 [fix] typo (#1655) 2022-03-17 10:00:29 +08:00
Kevin Wan
c11a09be23 chore: remove unnecessary env (#1654) 2022-03-16 17:31:10 +08:00
ronething-bot
56e1ecf2f3 fix: typo (#1646) 2022-03-15 17:46:13 +08:00
Kevin Wan
f9e6013a6c refactor: httpc package for easy to use (#1645) 2022-03-15 14:18:46 +08:00
Kevin Wan
b5d1d8b0d1 refactor: httpc package for easy to use (#1643) 2022-03-14 20:15:14 +08:00
xybingbing
09e6d94f9e FindOneBy 漏 Context (#1642) 2022-03-14 18:56:26 +08:00
Kevin Wan
2a5717d7fb feat: add httpc/Service for convinience (#1641) 2022-03-14 15:36:06 +08:00
Kevin Wan
85cf662c6f feat: add httpc/Get httpc/Post (#1640) 2022-03-13 14:49:14 +08:00
Kevin Wan
3279a7ef0f feat: add rest/httpc to make http requests governacible (#1638)
* feat: change x-trace-id to traceparent to follow opentelemetry

* feat: add rest/httpc to make http requests governacible

* chore: remove blank lines
2022-03-13 14:11:14 +08:00
Kevin Wan
fec908a19b Update ROADMAP.md
update roadmap.
2022-03-13 14:09:11 +08:00
Kevin Wan
f5ed0cda58 Update ROADMAP.md
update roadmap.
2022-03-13 14:08:28 +08:00
anqiansong
cc9d16f505 fix: Update unix-like path regex (#1637)
* Revert import value regex

* Update linux path regex

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-03-12 22:21:17 +08:00
Kevin Wan
c05d74b44c feat: support cpu stat on cgroups v2 (#1636)
* feat: cpu stat

* feat: add cpu stat for cgroup2

* feat: add cpu stat for cgroup2, tidy mod

* feat: support cpu stat in cgroup v2
2022-03-12 21:00:04 +08:00
mlr3000
32c88b6352 feat: support oracle :N dynamic parameters (#1552)
* chore:use struct pointer

* feat: support oracle :N dynamic parameters

* Update utils.go

* Update utils.go

* Update utils.go

pg argIndex will not always go up

* Update utils_test.go

* Keep the original

* Update utils_test.go
2022-03-12 18:49:07 +08:00
Kevin Wan
7dabec260f Update readme-cn.md
update readme.
2022-03-12 16:09:18 +08:00
Kevin Wan
4feb88f9b5 Update readme-cn.md
update readme.
2022-03-12 15:23:52 +08:00
Kevin Wan
2776caed0e Update readme.md
update readme.
2022-03-12 15:19:51 +08:00
chensy
c55694d957 Support for referencing types in different API files using format (#1630) 2022-03-12 15:17:31 +08:00
Ziyi Zhang
209ffb934b fix(goctl): kotlin code generation (#1632)
Signed-off-by: Ziyi Zhang <soasurs@gmail.com>
2022-03-11 13:44:18 +08:00
Kevin Wan
26a33932cd feat: support scratch as the base docker image (#1634) 2022-03-11 12:15:38 +08:00
Kevin Wan
d6a692971f chore: reduce the docker image size (#1633)
* chore: reduce the docker image size

* chore: format dockerfile
2022-03-11 11:30:21 +08:00
anqiansong
4624390e54 Fix #1585 #1547 (#1624) 2022-03-09 19:26:35 +08:00
Kevin Wan
63b7d292c1 chore: update goctl version to 1.3.3, change docker build temp dir (#1621) 2022-03-07 14:44:12 +08:00
Fyn
365c569d7c fix(goctl): dart gen user defined struct array (#1620) 2022-03-07 14:11:47 +08:00
anqiansong
68a81fea8a Fix #1609 (#1617) 2022-03-05 22:52:32 +08:00
anqiansong
08a8bd7ef7 Fix #1614 (#1616) 2022-03-05 21:40:41 +08:00
Kevin Wan
b939ce75ba chore: refactor code (#1613) 2022-03-04 17:55:13 +08:00
Kevin Wan
3b7ca86e4f chore: add unit tests (#1615)
* test: add more tests

* test: add more tests
2022-03-04 17:54:09 +08:00
Javen
60760b52ab model中db标签增加'-'符号以支持数据库查询时忽略对应字段. (#1612) 2022-03-04 17:00:46 +08:00
qi
96c128c58a fix: HitQuota should be returned instead of Allowed when limit is equal to 1. (#1581) 2022-03-04 16:14:45 +08:00
Fyn
0c35f39a7d fix: fix(gctl): apiparser_parser auto format (#1607) 2022-03-04 15:36:20 +08:00
Fyn
6a66dde0a1 feat(goctl): api dart support flutter v2 (#1603)
0. support null-safety code gen
1. supports -legacy flag for legacy code gen
2. supports -hostname flag for server hostname
3. use dart official format
4. fix some some bugs

Resolves: #1602
2022-03-04 15:34:13 +08:00
Kevin Wan
36b9fcba44 Update readme-cn.md 2022-03-03 14:35:48 +08:00
Kevin Wan
bf99dda620 Update readme-cn.md 2022-03-03 14:35:10 +08:00
Kevin Wan
511dfcb409 Update readme.md 2022-03-03 14:34:34 +08:00
Kevin Wan
900bc96420 test: add more tests (#1604) 2022-03-02 21:19:04 +08:00
Kevin Wan
be277a7376 Update readme-cn.md
add go-zero users.
2022-03-02 21:18:31 +08:00
Kevin Wan
f15a4f9188 chore: update go-zero to v1.3.1 in goctl (#1599) 2022-03-01 20:56:57 +08:00
148 changed files with 2812 additions and 977 deletions

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 xiaoheiban_server_go
Copyright (c) 2022 zeromicro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -23,6 +23,6 @@ We hope that the items listed below will inspire further engagement from the com
- [x] Support `context` in redis related methods for timeout and tracing
- [x] Support `context` in sql related methods for timeout and tracing
- [ ] Support `context` in mongodb related methods for timeout and tracing
- [ ] Add `httpx.Client` with governance, like circuit breaker etc.
- [x] Add `httpc.Do` with HTTP call governance, like circuit breaker etc.
- [ ] Support `goctl doctor` command to report potential issues for given service
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file

View File

@@ -171,7 +171,7 @@ func (lt loggedThrottle) allow() (Promise, error) {
func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool {
accept := acceptable(err)
if !accept {
if !accept && err != nil {
lt.errWin.add(err.Error())
}
return accept

View File

@@ -14,8 +14,8 @@ local window = tonumber(ARGV[2])
local current = redis.call("INCRBY", KEYS[1], 1)
if current == 1 then
redis.call("expire", KEYS[1], window)
return 1
elseif current < limit then
end
if current < limit then
return 1
elseif current == limit then
return 2

View File

@@ -65,3 +65,13 @@ func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
assert.Equal(t, 1, hitQuota)
assert.Equal(t, total-quota, overQuota)
}
func TestQuotaFull(t *testing.T) {
s, err := miniredis.Run()
assert.Nil(t, err)
l := NewPeriodLimit(1, 1, redis.New(s.Addr()), "periodlimit")
val, err := l.Take("first")
assert.Nil(t, err)
assert.Equal(t, HitQuota, val)
}

View File

@@ -448,7 +448,15 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
dereffedBaseType := Deref(baseType)
dereffedBaseKind := dereffedBaseType.Kind()
refValue := reflect.ValueOf(mapValue)
if refValue.IsNil() {
return nil
}
conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap())
if refValue.Len() == 0 {
value.Set(conv)
return nil
}
var valid bool
for i := 0; i < refValue.Len(); i++ {

View File

@@ -198,6 +198,49 @@ func TestUnmarshalIntWithDefault(t *testing.T) {
assert.Equal(t, 1, in.Int)
}
func TestUnmarshalBoolSliceRequired(t *testing.T) {
type inner struct {
Bools []bool `key:"bools"`
}
var in inner
assert.NotNil(t, UnmarshalKey(map[string]interface{}{}, &in))
}
func TestUnmarshalBoolSliceNil(t *testing.T) {
type inner struct {
Bools []bool `key:"bools,optional"`
}
var in inner
assert.Nil(t, UnmarshalKey(map[string]interface{}{}, &in))
assert.Nil(t, in.Bools)
}
func TestUnmarshalBoolSliceNilExplicit(t *testing.T) {
type inner struct {
Bools []bool `key:"bools,optional"`
}
var in inner
assert.Nil(t, UnmarshalKey(map[string]interface{}{
"bools": nil,
}, &in))
assert.Nil(t, in.Bools)
}
func TestUnmarshalBoolSliceEmpty(t *testing.T) {
type inner struct {
Bools []bool `key:"bools,optional"`
}
var in inner
assert.Nil(t, UnmarshalKey(map[string]interface{}{
"bools": []bool{},
}, &in))
assert.Empty(t, in.Bools)
}
func TestUnmarshalBoolSliceWithDefault(t *testing.T) {
type inner struct {
Bools []bool `key:"bools,default=[true,false]"`
@@ -330,28 +373,34 @@ func TestUnmarshalFloat(t *testing.T) {
func TestUnmarshalInt64Slice(t *testing.T) {
var v struct {
Ages []int64 `key:"ages"`
Ages []int64 `key:"ages"`
Slice []int64 `key:"slice"`
}
m := map[string]interface{}{
"ages": []int64{1, 2},
"ages": []int64{1, 2},
"slice": []interface{}{},
}
ast := assert.New(t)
ast.Nil(UnmarshalKey(m, &v))
ast.ElementsMatch([]int64{1, 2}, v.Ages)
ast.Equal([]int64{}, v.Slice)
}
func TestUnmarshalIntSlice(t *testing.T) {
var v struct {
Ages []int `key:"ages"`
Ages []int `key:"ages"`
Slice []int `key:"slice"`
}
m := map[string]interface{}{
"ages": []int{1, 2},
"ages": []int{1, 2},
"slice": []interface{}{},
}
ast := assert.New(t)
ast.Nil(UnmarshalKey(m, &v))
ast.ElementsMatch([]int{1, 2}, v.Ages)
ast.Equal([]int{}, v.Slice)
}
func TestUnmarshalString(t *testing.T) {

View File

@@ -11,6 +11,7 @@ const (
mega = 1024 * 1024
)
// DisplayStats prints the goroutine, memory, GC stats with given interval, default to 5 seconds.
func DisplayStats(interval ...time.Duration) {
duration := defaultInterval
for _, val := range interval {

View File

@@ -1,78 +1,129 @@
package internal
import (
"bufio"
"fmt"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/zeromicro/go-zero/core/iox"
"github.com/zeromicro/go-zero/core/lang"
"golang.org/x/sys/unix"
)
const cgroupDir = "/sys/fs/cgroup"
const (
cgroupDir = "/sys/fs/cgroup"
cpuStatFile = cgroupDir + "/cpu.stat"
cpusetFile = cgroupDir + "/cpuset.cpus.effective"
)
type cgroup struct {
var (
isUnifiedOnce sync.Once
isUnified bool
inUserNS bool
nsOnce sync.Once
)
type cgroup interface {
cpuQuotaUs() (int64, error)
cpuPeriodUs() (uint64, error)
cpus() ([]uint64, error)
usageAllCpus() (uint64, error)
}
func currentCgroup() (cgroup, error) {
if isCgroup2UnifiedMode() {
return currentCgroupV2()
}
return currentCgroupV1()
}
type cgroupV1 struct {
cgroups map[string]string
}
func (c *cgroup) acctUsageAllCpus() (uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpuacct"], "cpuacct.usage"))
if err != nil {
return 0, err
}
return parseUint(string(data))
}
func (c *cgroup) acctUsagePerCpu() ([]uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpuacct"], "cpuacct.usage_percpu"))
if err != nil {
return nil, err
}
var usage []uint64
for _, v := range strings.Fields(string(data)) {
u, err := parseUint(v)
if err != nil {
return nil, err
}
usage = append(usage, u)
}
return usage, nil
}
func (c *cgroup) cpuQuotaUs() (int64, error) {
func (c *cgroupV1) cpuQuotaUs() (int64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_quota_us"))
if err != nil {
return 0, err
}
return strconv.ParseInt(string(data), 10, 64)
return strconv.ParseInt(data, 10, 64)
}
func (c *cgroup) cpuPeriodUs() (uint64, error) {
func (c *cgroupV1) cpuPeriodUs() (uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_period_us"))
if err != nil {
return 0, err
}
return parseUint(string(data))
return parseUint(data)
}
func (c *cgroup) cpus() ([]uint64, error) {
func (c *cgroupV1) cpus() ([]uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpuset"], "cpuset.cpus"))
if err != nil {
return nil, err
}
return parseUints(string(data))
return parseUints(data)
}
func currentCgroup() (*cgroup, error) {
func (c *cgroupV1) usageAllCpus() (uint64, error) {
data, err := iox.ReadText(path.Join(c.cgroups["cpuacct"], "cpuacct.usage"))
if err != nil {
return 0, err
}
return parseUint(data)
}
type cgroupV2 struct {
cgroups map[string]string
}
func (c *cgroupV2) cpuQuotaUs() (int64, error) {
data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_quota_us"))
if err != nil {
return 0, err
}
return strconv.ParseInt(data, 10, 64)
}
func (c *cgroupV2) cpuPeriodUs() (uint64, error) {
data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_period_us"))
if err != nil {
return 0, err
}
return parseUint(data)
}
func (c *cgroupV2) cpus() ([]uint64, error) {
data, err := iox.ReadText(cpusetFile)
if err != nil {
return nil, err
}
return parseUints(data)
}
func (c *cgroupV2) usageAllCpus() (uint64, error) {
usec, err := parseUint(c.cgroups["usage_usec"])
if err != nil {
return 0, err
}
return usec * uint64(time.Microsecond), nil
}
func currentCgroupV1() (cgroup, error) {
cgroupFile := fmt.Sprintf("/proc/%d/cgroup", os.Getpid())
lines, err := iox.ReadTextLines(cgroupFile, iox.WithoutBlank())
if err != nil {
@@ -100,11 +151,51 @@ func currentCgroup() (*cgroup, error) {
}
}
return &cgroup{
return &cgroupV1{
cgroups: cgroups,
}, nil
}
func currentCgroupV2() (cgroup, error) {
lines, err := iox.ReadTextLines(cpuStatFile, iox.WithoutBlank())
if err != nil {
return nil, err
}
cgroups := make(map[string]string)
for _, line := range lines {
cols := strings.Fields(line)
if len(cols) != 2 {
return nil, fmt.Errorf("invalid cgroupV2 line: %s", line)
}
cgroups[cols[0]] = cols[1]
}
return &cgroupV2{
cgroups: cgroups,
}, nil
}
// isCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
func isCgroup2UnifiedMode() bool {
isUnifiedOnce.Do(func() {
var st unix.Statfs_t
err := unix.Statfs(cgroupDir, &st)
if err != nil {
if os.IsNotExist(err) && runningInUserNS() {
// ignore the "not found" error if running in userns
isUnified = false
return
}
panic(fmt.Sprintf("cannot statfs cgroup root: %s", err))
}
isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC
})
return isUnified
}
func parseUint(s string) (uint64, error) {
v, err := strconv.ParseInt(s, 10, 64)
if err != nil {
@@ -166,3 +257,36 @@ func parseUints(val string) ([]uint64, error) {
return sets, nil
}
// runningInUserNS detects whether we are currently running in a user namespace.
func runningInUserNS() bool {
nsOnce.Do(func() {
file, err := os.Open("/proc/self/uid_map")
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return
}
defer file.Close()
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return
}
line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
return
}
inUserNS = true
})
return inUserNS
}

View File

@@ -24,7 +24,7 @@ var (
// if /proc not present, ignore the cpu calculation, like wsl linux
func init() {
cpus, err := perCpuUsage()
cpus, err := cpuSets()
if err != nil {
logx.Error(err)
return
@@ -117,15 +117,6 @@ func cpuSets() ([]uint64, error) {
return cg.cpus()
}
func perCpuUsage() ([]uint64, error) {
cg, err := currentCgroup()
if err != nil {
return nil, err
}
return cg.acctUsagePerCpu()
}
func systemCpuUsage() (uint64, error) {
lines, err := iox.ReadTextLines("/proc/stat", iox.WithoutBlank())
if err != nil {
@@ -157,10 +148,10 @@ func systemCpuUsage() (uint64, error) {
}
func totalCpuUsage() (usage uint64, err error) {
var cg *cgroup
var cg cgroup
if cg, err = currentCgroup(); err != nil {
return
}
return cg.acctUsageAllCpus()
return cg.usageAllCpus()
}

View File

@@ -30,18 +30,32 @@ func RawFieldNames(in interface{}, postgresSql ...bool) []string {
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
if pg {
out = append(out, tagv)
} else {
out = append(out, fmt.Sprintf("`%s`", tagv))
}
} else {
tagv := fi.Tag.Get(dbTag)
switch tagv {
case "-":
continue
case "":
if pg {
out = append(out, fi.Name)
} else {
out = append(out, fmt.Sprintf("`%s`", fi.Name))
}
default:
// get tag name with the tag opton, e.g.:
// `db:"id"`
// `db:"id,type=char,length=16"`
// `db:",type=char,length=16"`
if strings.Contains(tagv, ",") {
tagv = strings.TrimSpace(strings.Split(tagv, ",")[0])
}
if len(tagv) == 0 {
tagv = fi.Name
}
if pg {
out = append(out, tagv)
} else {
out = append(out, fmt.Sprintf("`%s`", tagv))
}
}
}

View File

@@ -22,3 +22,20 @@ func TestFieldNames(t *testing.T) {
assert.Equal(t, expected, out)
})
}
type mockedUserWithOptions struct {
ID string `db:"id" json:"id,omitempty"`
UserName string `db:"user_name,type=varchar,length=255" json:"userName,omitempty"`
Sex int `db:"sex" json:"sex,omitempty"`
UUID string `db:",type=varchar,length=16" uuid:"uuid,omitempty"`
Age int `db:"age" json:"age"`
}
func TestFieldNamesWithTagOptions(t *testing.T) {
t.Run("new", func(t *testing.T) {
var u mockedUserWithOptions
out := RawFieldNames(&u)
expected := []string{"`id`", "`user_name`", "`sex`", "`UUID`", "`age`"}
assert.Equal(t, expected, out)
})
}

View File

@@ -24,6 +24,7 @@ type (
Expire(key string, seconds int) error
Expireat(key string, expireTime int64) error
Get(key string) (string, error)
GetSet(key, value string) (string, error)
Hdel(key, field string) (bool, error)
Hexists(key, field string) (bool, error)
Hget(key, field string) (string, error)
@@ -459,6 +460,15 @@ func (cs clusterStore) SetnxEx(key, value string, seconds int) (bool, error) {
return node.SetnxEx(key, value, seconds)
}
func (cs clusterStore) GetSet(key, value string) (string, error) {
node, err := cs.getRedis(key)
if err != nil {
return "", err
}
return node.GetSet(key, value)
}
func (cs clusterStore) Sismember(key string, value interface{}) (bool, error) {
node, err := cs.getRedis(key)
if err != nil {

View File

@@ -490,6 +490,29 @@ func TestRedis_SetExNx(t *testing.T) {
})
}
func TestRedis_Getset(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.GetSet("hello", "world")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
val, err := client.GetSet("hello", "world")
assert.Nil(t, err)
assert.Equal(t, "", val)
val, err = client.Get("hello")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.GetSet("hello", "newworld")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.Get("hello")
assert.Nil(t, err)
assert.Equal(t, "newworld", val)
_, err = client.Del("hello")
assert.Nil(t, err)
})
}
func TestRedis_SetGetDelHashField(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
err := store.Hset("key", "field", "value")

View File

@@ -12,8 +12,8 @@ var (
ErrNotFound = mgo.ErrNotFound
// can't use one SingleFlight per conn, because multiple conns may share the same cache key.
sharedCalls = syncx.NewSingleFlight()
stats = cache.NewStat("mongoc")
singleFlight = syncx.NewSingleFlight()
stats = cache.NewStat("mongoc")
)
type (

View File

@@ -34,7 +34,7 @@ func TestCollection_Count(t *testing.T) {
assert.Nil(t, err)
defer clean()
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
val, err := c.Count("any")
assert.Nil(t, err)
@@ -98,7 +98,7 @@ func TestStat(t *testing.T) {
assert.Nil(t, err)
defer clean()
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach).(*cachedCollection)
for i := 0; i < 10; i++ {
@@ -121,7 +121,7 @@ func TestStatCacheFails(t *testing.T) {
defer log.SetOutput(os.Stdout)
r := redis.New("localhost:59999")
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
for i := 0; i < 20; i++ {
@@ -142,7 +142,7 @@ func TestStatDbFails(t *testing.T) {
assert.Nil(t, err)
defer clean()
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach).(*cachedCollection)
for i := 0; i < 20; i++ {
@@ -164,7 +164,7 @@ func TestStatFromMemory(t *testing.T) {
assert.Nil(t, err)
defer clean()
cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach).(*cachedCollection)
var all sync.WaitGroup

View File

@@ -38,7 +38,7 @@ func MustNewModel(url, collection string, c cache.CacheConf, opts ...cache.Optio
// NewModel returns a Model with a cache cluster.
func NewModel(url, collection string, conf cache.CacheConf, opts ...cache.Option) (*Model, error) {
c := cache.New(conf, sharedCalls, stats, mgo.ErrNotFound, opts...)
c := cache.New(conf, singleFlight, stats, mgo.ErrNotFound, opts...)
return NewModelWithCache(url, collection, c)
}
@@ -51,7 +51,7 @@ func NewModelWithCache(url, collection string, c cache.Cache) (*Model, error) {
// NewNodeModel returns a Model with a cache node.
func NewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) (*Model, error) {
c := cache.NewNode(rds, sharedCalls, stats, mgo.ErrNotFound, opts...)
c := cache.NewNode(rds, singleFlight, stats, mgo.ErrNotFound, opts...)
return NewModelWithCache(url, collection, c)
}

View File

@@ -640,6 +640,29 @@ func (s *Redis) GetBitCtx(ctx context.Context, key string, offset int64) (val in
return
}
// GetSet is the implementation of redis getset command.
func (s *Redis) GetSet(key, value string) (string, error) {
return s.GetSetCtx(context.Background(), key, value)
}
// GetSetCtx is the implementation of redis getset command.
func (s *Redis) GetSetCtx(ctx context.Context, key, value string) (val string, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if val, err = conn.GetSet(ctx, key, value).Result(); err == red.Nil {
return nil
}
return err
}, acceptable)
return
}
// Hdel is the implementation of redis hdel command.
func (s *Redis) Hdel(key string, fields ...string) (bool, error) {
return s.HdelCtx(context.Background(), key, fields...)
@@ -1381,21 +1404,28 @@ func (s *Redis) ScanCtx(ctx context.Context, cursor uint64, match string, count
}
// SetBit is the implementation of redis setbit command.
func (s *Redis) SetBit(key string, offset int64, value int) error {
func (s *Redis) SetBit(key string, offset int64, value int) (int, error) {
return s.SetBitCtx(context.Background(), key, offset, value)
}
// SetBitCtx is the implementation of redis setbit command.
func (s *Redis) SetBitCtx(ctx context.Context, key string, offset int64, value int) error {
return s.brk.DoWithAcceptable(func() error {
func (s *Redis) SetBitCtx(ctx context.Context, key string, offset int64, value int) (val int, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
_, err = conn.SetBit(ctx, key, offset, value).Result()
return err
v, err := conn.SetBit(ctx, key, offset, value).Result()
if err != nil {
return err
}
val = int(v)
return nil
}, acceptable)
return
}
// Sscan is the implementation of redis sscan command.

View File

@@ -387,30 +387,33 @@ func TestRedis_Mget(t *testing.T) {
func TestRedis_SetBit(t *testing.T) {
runOnRedis(t, func(client *Redis) {
err := New(client.Addr, badType()).SetBit("key", 1, 1)
_, err := New(client.Addr, badType()).SetBit("key", 1, 1)
assert.NotNil(t, err)
err = client.SetBit("key", 1, 1)
val, err := client.SetBit("key", 1, 1)
assert.Nil(t, err)
assert.Equal(t, 0, val)
})
}
func TestRedis_GetBit(t *testing.T) {
runOnRedis(t, func(client *Redis) {
err := client.SetBit("key", 2, 1)
val, err := client.SetBit("key", 2, 1)
assert.Nil(t, err)
assert.Equal(t, 0, val)
_, err = New(client.Addr, badType()).GetBit("key", 2)
assert.NotNil(t, err)
val, err := client.GetBit("key", 2)
v, err := client.GetBit("key", 2)
assert.Nil(t, err)
assert.Equal(t, 1, val)
assert.Equal(t, 1, v)
})
}
func TestRedis_BitCount(t *testing.T) {
runOnRedis(t, func(client *Redis) {
for i := 0; i < 11; i++ {
err := client.SetBit("key", int64(i), 1)
val, err := client.SetBit("key", int64(i), 1)
assert.Nil(t, err)
assert.Equal(t, 0, val)
}
_, err := New(client.Addr, badType()).BitCount("key", 0, -1)
@@ -701,6 +704,28 @@ func TestRedis_Set(t *testing.T) {
})
}
func TestRedis_GetSet(t *testing.T) {
runOnRedis(t, func(client *Redis) {
_, err := New(client.Addr, badType()).GetSet("hello", "world")
assert.NotNil(t, err)
val, err := client.GetSet("hello", "world")
assert.Nil(t, err)
assert.Equal(t, "", val)
val, err = client.Get("hello")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.GetSet("hello", "newworld")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.Get("hello")
assert.Nil(t, err)
assert.Equal(t, "newworld", val)
ret, err := client.Del("hello")
assert.Nil(t, err)
assert.Equal(t, 1, ret)
})
}
func TestRedis_SetGetDel(t *testing.T) {
runOnRedis(t, func(client *Redis) {
err := New(client.Addr, badType()).Set("hello", "world")

View File

@@ -2,6 +2,7 @@ package redis
import (
"math/rand"
"strconv"
"sync/atomic"
"time"
@@ -11,19 +12,26 @@ import (
)
const (
randomLen = 16
tolerance = 500 // milliseconds
millisPerSecond = 1000
lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
return "OK"
else
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end`
delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end`
randomLen = 16
)
// A RedisLock is a redis lock.
type RedisLock struct {
store *Redis
seconds uint32
count int32
key string
id string
}
@@ -43,35 +51,30 @@ func NewRedisLock(store *Redis, key string) *RedisLock {
// Acquire acquires the lock.
func (rl *RedisLock) Acquire() (bool, error) {
newCount := atomic.AddInt32(&rl.count, 1)
if newCount > 1 {
seconds := atomic.LoadUint32(&rl.seconds)
resp, err := rl.store.Eval(lockCommand, []string{rl.key}, []string{
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
})
if err == red.Nil {
return false, nil
} else if err != nil {
logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
return false, err
} else if resp == nil {
return false, nil
}
reply, ok := resp.(string)
if ok && reply == "OK" {
return true, nil
}
seconds := atomic.LoadUint32(&rl.seconds)
ok, err := rl.store.SetnxEx(rl.key, rl.id, int(seconds+1)) // +1s for tolerance
if err == red.Nil {
atomic.AddInt32(&rl.count, -1)
return false, nil
} else if err != nil {
atomic.AddInt32(&rl.count, -1)
logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
return false, err
} else if !ok {
atomic.AddInt32(&rl.count, -1)
return false, nil
}
return true, nil
logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
return false, nil
}
// Release releases the lock.
func (rl *RedisLock) Release() (bool, error) {
newCount := atomic.AddInt32(&rl.count, -1)
if newCount > 0 {
return true, nil
}
resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
if err != nil {
return false, err

View File

@@ -29,25 +29,5 @@ func TestRedisLock(t *testing.T) {
endAcquire, err := secondLock.Acquire()
assert.Nil(t, err)
assert.True(t, endAcquire)
endAcquire, err = secondLock.Acquire()
assert.Nil(t, err)
assert.True(t, endAcquire)
release, err = secondLock.Release()
assert.Nil(t, err)
assert.True(t, release)
againAcquire, err = firstLock.Acquire()
assert.Nil(t, err)
assert.False(t, againAcquire)
release, err = secondLock.Release()
assert.Nil(t, err)
assert.True(t, release)
firstAcquire, err = firstLock.Acquire()
assert.Nil(t, err)
assert.True(t, firstAcquire)
})
}

View File

@@ -25,6 +25,7 @@ type (
// A BulkInserter is used to batch insert records.
// Postgresql is not supported yet, because of the sql is formated with symbol `$`.
// Oracle is not supported yet, because of the sql is formated with symbol `:`.
BulkInserter struct {
executor *executors.PeriodicalExecutor
inserter *dbInserter

View File

@@ -67,7 +67,7 @@ func format(query string, args ...interface{}) (string, error) {
writeValue(&b, args[argIndex])
argIndex++
case '$':
case ':', '$':
var j int
for j = i + 1; j < bytes; j++ {
char := query[j]
@@ -75,16 +75,18 @@ func format(query string, args ...interface{}) (string, error) {
break
}
}
if j > i+1 {
index, err := strconv.Atoi(query[i+1 : j])
if err != nil {
return "", err
}
// index starts from 1 for pg
// index starts from 1 for pg or oracle
if index > argIndex {
argIndex = index
}
index--
if index < 0 || numArgs <= index {
return "", fmt.Errorf("error: wrong index %d in sql", index)

View File

@@ -73,6 +73,30 @@ func TestFormat(t *testing.T) {
args: []interface{}{"133", false},
hasErr: true,
},
{
name: "oracle normal",
query: "select name, age from users where bool=:1 and phone=:2",
args: []interface{}{true, "133"},
expect: "select name, age from users where bool=1 and phone='133'",
},
{
name: "oracle normal reverse",
query: "select name, age from users where bool=:2 and phone=:1",
args: []interface{}{"133", false},
expect: "select name, age from users where bool=0 and phone='133'",
},
{
name: "oracle error not number",
query: "select name, age from users where bool=:a and phone=:1",
args: []interface{}{"133", false},
hasErr: true,
},
{
name: "oracle error more args",
query: "select name, age from users where bool=:2 and phone=:1 and nickname=:3",
args: []interface{}{"133", false},
hasErr: true,
},
}
for _, test := range tests {

View File

@@ -90,14 +90,14 @@ func TestParseFullMethod(t *testing.T) {
semconv.RPCMethodKey.String("theMethod"),
},
}, {
fullMethod: "/pkg.srv",
name: "pkg.srv",
fullMethod: "/pkg.svr",
name: "pkg.svr",
attr: []attribute.KeyValue(nil),
}, {
fullMethod: "/pkg.srv/",
name: "pkg.srv/",
fullMethod: "/pkg.svr/",
name: "pkg.svr/",
attr: []attribute.KeyValue{
semconv.RPCServiceKey.String("pkg.srv"),
semconv.RPCServiceKey.String("pkg.svr"),
},
},
}

9
go.mod
View File

@@ -42,13 +42,20 @@ require (
github.com/fatih/color v1.10.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-redis/redis/v8 v8.11.4
github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/openzipkin/zipkin-go v0.4.0 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 // indirect
k8s.io/klog/v2 v2.40.1 // indirect
)

110
go.sum
View File

@@ -9,17 +9,27 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T
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/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
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=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
@@ -52,6 +62,7 @@ github.com/alicebob/miniredis/v2 v2.17.0 h1:EwLdrIS50uczw71Jc7iVSxZluTKj5nfSP8n7
github.com/alicebob/miniredis/v2 v2.17.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -164,6 +175,8 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
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=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -171,6 +184,7 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
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=
@@ -189,21 +203,27 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
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/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -238,8 +258,9 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@@ -274,15 +295,17 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -332,13 +355,15 @@ github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
@@ -372,27 +397,24 @@ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/X
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
go.etcd.io/etcd/api/v3 v3.5.1 h1:v28cktvBq+7vGyJXF8G+rWJmj+1XUmMtqcLnH8hDocM=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/api/v3 v3.5.2 h1:tXok5yLlKyuQ/SXSjtqHc4uzNaMqZi2XsoSPr/LlJXI=
go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.1 h1:XIQcHCFSG53bJETYeRJtIxdLv2EWRGxcfzR8lSnTH4E=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/pkg/v3 v3.5.2 h1:4hzqQ6hIb3blLyQ8usCU4h3NghkqcsohEQ3o3VetYxE=
go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.1 h1:oImGuV5LGKjCqXdjkMHCyWa5OO1gYKCnC/1sgdfj1Uk=
go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA=
go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0=
@@ -404,7 +426,6 @@ go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1t
go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -413,11 +434,9 @@ go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhW
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
@@ -479,6 +498,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -488,31 +508,39 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
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/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -547,8 +575,14 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
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-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -562,8 +596,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -614,8 +646,18 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
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-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@@ -636,13 +678,21 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
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.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
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/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -660,12 +710,20 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
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=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
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-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220211171837-173942840c17 h1:2X+CNIheCutWRyKRte8szGxrE5ggtV4U+NKAbh/oLhg=
google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I=
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -676,6 +734,10 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
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/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
@@ -690,6 +752,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
@@ -729,6 +792,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
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/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.20.12 h1:LfRpmRkJLwPP8eaYehsVVmIIfg1yCBIIUHaSsdqCgHA=
k8s.io/api v0.20.12/go.mod h1:A2brwyEkVLM3wQGNnzoAa5JsQRzHK0uoOQ+bsnv7V68=
k8s.io/apimachinery v0.20.12 h1:2c0LIVNMvB8k2Ozstmhl2zGeCEcPazznuLYEwxFdNjM=

View File

@@ -2,6 +2,8 @@
# go-zero
***缩短从需求到上线的距离***
[English](readme.md) | 简体中文
[![Go](https://github.com/zeromicro/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/zeromicro/go-zero/actions)
@@ -11,17 +13,13 @@
[![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
> ***缩短从需求到上线的距离***
**为了满足开源基金会要求go-zero 从好未来tal-tech组织下迁移至中立的 GitHub 组织zeromicro。**
> ***注意:***
>
> 从 v1.3.0 之前版本升级请执行以下命令:
>
> GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
> `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
>
> goctl migrate —verbose —version v1.3.0
> `goctl migrate —verbose —version v1.3.1`
## 0. go-zero 介绍
@@ -252,6 +250,8 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
>56. 时代脉搏网络科技(云浮市)有限公司
>57. 店有帮
>58. 七牛云
>59. 费芮网络
>60. 51CTO
如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
@@ -281,3 +281,9 @@ go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape
加群之前有劳点一下 ***star***,一个小小的 ***star*** 是作者们回答海量问题的动力!🤝
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
## 12. 赞助一下👍
如果觉得项目有帮助,可以请作者喝杯咖啡 🍹
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/sponsor.png" alt="wechat" width="300" />

View File

@@ -12,15 +12,13 @@ English | [简体中文](readme-cn.md)
[![Discord](https://img.shields.io/discord/794530774463414292?label=chat&logo=discord)](https://discord.gg/4JQvC5A4Fe)
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
**Note: To meet the requirements of Open Source Foundation, we moved go-zero from tal-tech to zeromicro (a neutral GitHub organization).**
> ***Important!***
>
> To upgrade from previous versions, run the following commands.
> To upgrade from versions eariler than v1.3.0, run the following commands.
>
> `go install github.com/zeromicro/go-zero/tools/goctl@latest`
>
> `goctl migrate —verbose —version v1.3.0`
> `goctl migrate —verbose —version v1.3.1`
## 0. what is go-zero

View File

@@ -35,16 +35,16 @@ type engine struct {
}
func newEngine(c RestConf) *engine {
srv := &engine{
svr := &engine{
conf: c,
}
if c.CpuThreshold > 0 {
srv.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
srv.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
svr.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
svr.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
(c.CpuThreshold + topCpuUsage) >> 1))
}
return srv
return svr
}
func (ng *engine) addRoutes(r featuredRoutes) {
@@ -94,7 +94,7 @@ func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *sta
handler.TimeoutHandler(ng.checkedTimeout(fr.timeout)),
handler.RecoverHandler,
handler.MetricHandler(metrics),
handler.MaxBytesHandler(ng.conf.MaxBytes),
handler.MaxBytesHandler(ng.checkedMaxBytes(fr.maxBytes)),
handler.GunzipHandler,
)
chain = ng.appendAuthHandler(fr, chain, verifier)
@@ -119,6 +119,14 @@ func (ng *engine) bindRoutes(router httpx.Router) error {
return nil
}
func (ng *engine) checkedMaxBytes(bytes int64) int64 {
if bytes > 0 {
return bytes
}
return ng.conf.MaxBytes
}
func (ng *engine) checkedTimeout(timeout time.Duration) time.Duration {
if timeout > 0 {
return timeout
@@ -238,9 +246,9 @@ func (ng *engine) start(router httpx.Router) error {
}
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
ng.conf.KeyFile, router, func(srv *http.Server) {
ng.conf.KeyFile, router, func(svr *http.Server) {
if ng.tlsConfig != nil {
srv.TLSConfig = ng.tlsConfig
svr.TLSConfig = ng.tlsConfig
}
})
}

View File

@@ -194,6 +194,41 @@ func TestEngine_checkedTimeout(t *testing.T) {
}
}
func TestEngine_checkedMaxBytes(t *testing.T) {
tests := []struct {
name string
maxBytes int64
expect int64
}{
{
name: "not set",
expect: 1000,
},
{
name: "less",
maxBytes: 500,
expect: 500,
},
{
name: "equal",
maxBytes: 1000,
expect: 1000,
},
{
name: "more",
maxBytes: 1500,
expect: 1500,
},
}
ng := newEngine(RestConf{
MaxBytes: 1000,
})
for _, test := range tests {
assert.Equal(t, test.expect, ng.checkedMaxBytes(test.maxBytes))
}
}
func TestEngine_notFoundHandler(t *testing.T) {
logx.Disable()

View File

@@ -32,11 +32,7 @@ func TracingHandler(serviceName, path string) func(http.Handler) http.Handler {
defer span.End()
// convenient for tracking error messages
sc := span.SpanContext()
if sc.HasTraceID() {
w.Header().Set(trace.TraceIdKey, sc.TraceID().String())
}
propagator.Inject(spanCtx, propagation.HeaderCarrier(w.Header()))
next.ServeHTTP(w, r.WithContext(spanCtx))
})
}

View File

@@ -0,0 +1,8 @@
package internal
import "net/http"
type (
Interceptor func(r *http.Request) (*http.Request, ResponseHandler)
ResponseHandler func(resp *http.Response, err error)
)

View File

@@ -0,0 +1,34 @@
package internal
import (
"net/http"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/timex"
"go.opentelemetry.io/otel/propagation"
)
func LogInterceptor(r *http.Request) (*http.Request, ResponseHandler) {
start := timex.Now()
return r, func(resp *http.Response, err error) {
duration := timex.Since(start)
if err != nil {
logger := logx.WithContext(r.Context()).WithDuration(duration)
logger.Errorf("[HTTP] %s %s - %v", r.Method, r.URL, err)
return
}
var tc propagation.TraceContext
ctx := tc.Extract(r.Context(), propagation.HeaderCarrier(resp.Header))
logger := logx.WithContext(ctx).WithDuration(duration)
if isOkResponse(resp.StatusCode) {
logger.Infof("[HTTP] %d - %s %s", resp.StatusCode, r.Method, r.URL)
} else {
logger.Errorf("[HTTP] %d - %s %s", resp.StatusCode, r.Method, r.URL)
}
}
}
func isOkResponse(code int) bool {
return code < http.StatusBadRequest
}

View File

@@ -0,0 +1,51 @@
package internal
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLogInterceptor(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
req, handler := LogInterceptor(req)
resp, err := http.DefaultClient.Do(req)
handler(resp, err)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestLogInterceptorServerError(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
req, handler := LogInterceptor(req)
resp, err := http.DefaultClient.Do(req)
handler(resp, err)
assert.Nil(t, err)
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
}
func TestLogInterceptorServerClosed(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
svr.Close()
req, handler := LogInterceptor(req)
resp, err := http.DefaultClient.Do(req)
handler(resp, err)
assert.NotNil(t, err)
assert.Nil(t, resp)
}

44
rest/httpc/requests.go Normal file
View File

@@ -0,0 +1,44 @@
package httpc
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpc/internal"
)
var interceptors = []internal.Interceptor{
internal.LogInterceptor,
}
// DoRequest sends an HTTP request and returns an HTTP response.
func DoRequest(r *http.Request) (*http.Response, error) {
return request(r, defaultClient{})
}
type (
client interface {
do(r *http.Request) (*http.Response, error)
}
defaultClient struct{}
)
func (c defaultClient) do(r *http.Request) (*http.Response, error) {
return http.DefaultClient.Do(r)
}
func request(r *http.Request, cli client) (*http.Response, error) {
var respHandlers []internal.ResponseHandler
for _, interceptor := range interceptors {
var h internal.ResponseHandler
r, h = interceptor(r)
respHandlers = append(respHandlers, h)
}
resp, err := cli.do(r)
for i := len(respHandlers) - 1; i >= 0; i-- {
respHandlers[i](resp, err)
}
return resp, err
}

View File

@@ -0,0 +1,41 @@
package httpc
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDo(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestDoNotFound(t *testing.T) {
svr := httptest.NewServer(http.NotFoundHandler())
defer svr.Close()
req, err := http.NewRequest(http.MethodPost, svr.URL, nil)
assert.Nil(t, err)
req.Header.Set("Content-Type", "application/json")
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}
func TestDoMoved(t *testing.T) {
svr := httptest.NewServer(http.RedirectHandler("/foo", http.StatusMovedPermanently))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
_, err = DoRequest(req)
// too many redirects
assert.NotNil(t, err)
}

36
rest/httpc/responses.go Normal file
View File

@@ -0,0 +1,36 @@
package httpc
import (
"net/http"
"strings"
"github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/rest/internal/encoding"
)
// Parse parses the response.
func Parse(resp *http.Response, val interface{}) error {
if err := ParseHeaders(resp, val); err != nil {
return err
}
return ParseJsonBody(resp, val)
}
// ParseHeaders parses the rsponse headers.
func ParseHeaders(resp *http.Response, val interface{}) error {
return encoding.ParseHeaders(resp.Header, val)
}
// ParseJsonBody parses the rsponse body, which should be in json content type.
func ParseJsonBody(resp *http.Response, val interface{}) error {
if withJsonBody(resp) {
return mapping.UnmarshalJsonReader(resp.Body, val)
}
return mapping.UnmarshalJsonMap(nil, val)
}
func withJsonBody(r *http.Response) bool {
return r.ContentLength > 0 && strings.Contains(r.Header.Get(contentType), applicationJson)
}

View File

@@ -0,0 +1,64 @@
package httpc
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParse(t *testing.T) {
var val struct {
Foo string `header:"foo"`
Name string `json:"name"`
Value int `json:"value"`
}
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("foo", "bar")
w.Header().Set(contentType, applicationJson)
w.Write([]byte(`{"name":"kevin","value":100}`))
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.Nil(t, Parse(resp, &val))
assert.Equal(t, "bar", val.Foo)
assert.Equal(t, "kevin", val.Name)
assert.Equal(t, 100, val.Value)
}
func TestParseHeaderError(t *testing.T) {
var val struct {
Foo int `header:"foo"`
}
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("foo", "bar")
w.Header().Set(contentType, applicationJson)
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.NotNil(t, Parse(resp, &val))
}
func TestParseNoBody(t *testing.T) {
var val struct {
Foo string `header:"foo"`
}
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("foo", "bar")
w.Header().Set(contentType, applicationJson)
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err)
assert.Nil(t, Parse(resp, &val))
assert.Equal(t, "bar", val.Foo)
}

61
rest/httpc/service.go Normal file
View File

@@ -0,0 +1,61 @@
package httpc
import (
"net/http"
"github.com/zeromicro/go-zero/core/breaker"
)
type (
// Option is used to customize the *http.Client.
Option func(r *http.Request) *http.Request
// Service represents a remote HTTP service.
Service interface {
// DoRequest sends a HTTP request to the service.
DoRequest(r *http.Request) (*http.Response, error)
}
namedService struct {
name string
cli *http.Client
opts []Option
}
)
// NewService returns a remote service with the given name.
// opts are used to customize the *http.Client.
func NewService(name string, opts ...Option) Service {
return NewServiceWithClient(name, http.DefaultClient, opts...)
}
// NewServiceWithClient returns a remote service with the given name.
// opts are used to customize the *http.Client.
func NewServiceWithClient(name string, cli *http.Client, opts ...Option) Service {
return namedService{
name: name,
cli: cli,
opts: opts,
}
}
// DoRequest sends an HTTP request to the service.
func (s namedService) DoRequest(r *http.Request) (*http.Response, error) {
return request(r, s)
}
func (s namedService) do(r *http.Request) (resp *http.Response, err error) {
for _, opt := range s.opts {
r = opt(r)
}
brk := breaker.GetBreaker(s.name)
err = brk.DoWithAcceptable(func() error {
resp, err = s.cli.Do(r)
return err
}, func(err error) bool {
return err == nil && resp.StatusCode < http.StatusInternalServerError
})
return
}

View File

@@ -0,0 +1,49 @@
package httpc
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNamedService_Do(t *testing.T) {
svr := httptest.NewServer(http.RedirectHandler("/foo", http.StatusMovedPermanently))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
service := NewService("foo")
_, err = service.DoRequest(req)
// too many redirects
assert.NotNil(t, err)
}
func TestNamedService_Get(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("foo", r.Header.Get("foo"))
}))
defer svr.Close()
service := NewService("foo", func(r *http.Request) *http.Request {
r.Header.Set("foo", "bar")
return r
})
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := service.DoRequest(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "bar", resp.Header.Get("foo"))
}
func TestNamedService_Post(t *testing.T) {
svr := httptest.NewServer(http.NotFoundHandler())
defer svr.Close()
service := NewService("foo")
req, err := http.NewRequest(http.MethodPost, svr.URL, nil)
assert.Nil(t, err)
req.Header.Set("Content-Type", "application/json")
resp, err := service.DoRequest(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}

6
rest/httpc/vars.go Normal file
View File

@@ -0,0 +1,6 @@
package httpc
const (
contentType = "Content-Type"
applicationJson = "application/json"
)

View File

@@ -3,17 +3,16 @@ package httpx
import (
"io"
"net/http"
"net/textproto"
"strings"
"github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/rest/internal/encoding"
"github.com/zeromicro/go-zero/rest/pathvar"
)
const (
formKey = "form"
pathKey = "path"
headerKey = "header"
maxMemory = 32 << 20 // 32MB
maxBodyLen = 8 << 20 // 8MB
separator = ";"
@@ -21,10 +20,8 @@ const (
)
var (
formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues())
pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues())
headerUnmarshaler = mapping.NewUnmarshaler(headerKey, mapping.WithStringValues(),
mapping.WithCanonicalKeyFunc(textproto.CanonicalMIMEHeaderKey))
formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues())
pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues())
)
// Parse parses the request.
@@ -46,16 +43,7 @@ func Parse(r *http.Request, v interface{}) error {
// ParseHeaders parses the headers request.
func ParseHeaders(r *http.Request, v interface{}) error {
m := map[string]interface{}{}
for k, v := range r.Header {
if len(v) == 1 {
m[k] = v[0]
} else {
m[k] = v
}
}
return headerUnmarshaler.Unmarshal(m, v)
return encoding.ParseHeaders(r.Header, v)
}
// ParseForm parses the form request.

View File

@@ -0,0 +1,27 @@
package encoding
import (
"net/http"
"net/textproto"
"github.com/zeromicro/go-zero/core/mapping"
)
const headerKey = "header"
var headerUnmarshaler = mapping.NewUnmarshaler(headerKey, mapping.WithStringValues(),
mapping.WithCanonicalKeyFunc(textproto.CanonicalMIMEHeaderKey))
// ParseHeaders parses the headers request.
func ParseHeaders(header http.Header, v interface{}) error {
m := map[string]interface{}{}
for k, v := range header {
if len(v) == 1 {
m[k] = v[0]
} else {
m[k] = v
}
}
return headerUnmarshaler.Unmarshal(m, v)
}

View File

@@ -0,0 +1,40 @@
package encoding
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseHeaders(t *testing.T) {
var val struct {
Foo string `header:"foo"`
Baz int `header:"baz"`
Qux bool `header:"qux,default=true"`
}
r := httptest.NewRequest(http.MethodGet, "/any", nil)
r.Header.Set("foo", "bar")
r.Header.Set("baz", "1")
assert.Nil(t, ParseHeaders(r.Header, &val))
assert.Equal(t, "bar", val.Foo)
assert.Equal(t, 1, val.Baz)
assert.True(t, val.Qux)
}
func TestParseHeadersMulti(t *testing.T) {
var val struct {
Foo []string `header:"foo"`
Baz int `header:"baz"`
Qux bool `header:"qux,default=true"`
}
r := httptest.NewRequest(http.MethodGet, "/any", nil)
r.Header.Set("foo", "bar")
r.Header.Add("foo", "bar1")
r.Header.Set("baz", "1")
assert.Nil(t, ParseHeaders(r.Header, &val))
assert.Equal(t, []string{"bar", "bar1"}, val.Foo)
assert.Equal(t, 1, val.Baz)
assert.True(t, val.Qux)
}

View File

@@ -36,3 +36,8 @@ func TestError(t *testing.T) {
assert.True(t, strings.Contains(val, "third"))
assert.True(t, strings.Contains(val, "\n"))
}
func TestContextKey_String(t *testing.T) {
val := contextKey("foo")
assert.True(t, strings.Contains(val.String(), "foo"))
}

View File

@@ -151,6 +151,8 @@ func TestContentSecurity(t *testing.T) {
return
}
encrypted := test.mode != "0"
assert.Equal(t, encrypted, header.Encrypted())
assert.Equal(t, test.code, VerifySignature(r, header, time.Minute))
})
}

View File

@@ -10,25 +10,25 @@ import (
)
// StartOption defines the method to customize http.Server.
type StartOption func(srv *http.Server)
type StartOption func(svr *http.Server)
// StartHttp starts a http server.
func StartHttp(host string, port int, handler http.Handler, opts ...StartOption) error {
return start(host, port, handler, func(srv *http.Server) error {
return srv.ListenAndServe()
return start(host, port, handler, func(svr *http.Server) error {
return svr.ListenAndServe()
}, opts...)
}
// StartHttps starts a https server.
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler,
opts ...StartOption) error {
return start(host, port, handler, func(srv *http.Server) error {
return start(host, port, handler, func(svr *http.Server) error {
// certFile and keyFile are set in buildHttpsServer
return srv.ListenAndServeTLS(certFile, keyFile)
return svr.ListenAndServeTLS(certFile, keyFile)
}, opts...)
}
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error,
func start(host string, port int, handler http.Handler, run func(svr *http.Server) error,
opts ...StartOption) (err error) {
server := &http.Server{
Addr: fmt.Sprintf("%s:%d", host, port),

View File

@@ -0,0 +1,33 @@
package internal
import (
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStartHttp(t *testing.T) {
svr := httptest.NewUnstartedServer(http.NotFoundHandler())
fields := strings.Split(svr.Listener.Addr().String(), ":")
port, err := strconv.Atoi(fields[1])
assert.Nil(t, err)
err = StartHttp(fields[0], port, http.NotFoundHandler(), func(svr *http.Server) {
svr.IdleTimeout = 0
})
assert.NotNil(t, err)
}
func TestStartHttps(t *testing.T) {
svr := httptest.NewTLSServer(http.NotFoundHandler())
fields := strings.Split(svr.Listener.Addr().String(), ":")
port, err := strconv.Atoi(fields[1])
assert.Nil(t, err)
err = StartHttps(fields[0], port, "", "", http.NotFoundHandler(), func(svr *http.Server) {
svr.IdleTimeout = 0
})
assert.NotNil(t, err)
}

View File

@@ -137,6 +137,13 @@ func WithJwtTransition(secret, prevSecret string) RouteOption {
}
}
// WithMaxBytes returns a RouteOption to set maxBytes with the given value.
func WithMaxBytes(maxBytes int64) RouteOption {
return func(r *featuredRoutes) {
r.maxBytes = maxBytes
}
}
// WithMiddlewares adds given middlewares to given routes.
func WithMiddlewares(ms []Middleware, rs ...Route) []Route {
for i := len(ms) - 1; i >= 0; i-- {
@@ -225,22 +232,22 @@ func WithTimeout(timeout time.Duration) RouteOption {
// WithTLSConfig returns a RunOption that with given tls config.
func WithTLSConfig(cfg *tls.Config) RunOption {
return func(srv *Server) {
srv.ngin.setTlsConfig(cfg)
return func(svr *Server) {
svr.ngin.setTlsConfig(cfg)
}
}
// WithUnauthorizedCallback returns a RunOption that with given unauthorized callback set.
func WithUnauthorizedCallback(callback handler.UnauthorizedCallback) RunOption {
return func(srv *Server) {
srv.ngin.setUnauthorizedCallback(callback)
return func(svr *Server) {
svr.ngin.setUnauthorizedCallback(callback)
}
}
// WithUnsignedCallback returns a RunOption that with given unsigned callback set.
func WithUnsignedCallback(callback handler.UnsignedCallback) RunOption {
return func(srv *Server) {
srv.ngin.setUnsignedCallback(callback)
return func(svr *Server) {
svr.ngin.setUnsignedCallback(callback)
}
}

View File

@@ -56,22 +56,22 @@ Port: 54321
}
for _, test := range tests {
var srv *Server
var svr *Server
var err error
if test.fail {
_, err = NewServer(test.c, test.opts...)
assert.NotNil(t, err)
continue
} else {
srv = MustNewServer(test.c, test.opts...)
svr = MustNewServer(test.c, test.opts...)
}
srv.Use(ToMiddleware(func(next http.Handler) http.Handler {
svr.Use(ToMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}))
srv.AddRoute(Route{
svr.AddRoute(Route{
Method: http.MethodGet,
Path: "/",
Handler: nil,
@@ -89,12 +89,19 @@ Port: 54321
}
}()
srv.Start()
srv.Stop()
svr.Start()
svr.Stop()
}()
}
}
func TestWithMaxBytes(t *testing.T) {
const maxBytes = 1000
var fr featuredRoutes
WithMaxBytes(maxBytes)(&fr)
assert.Equal(t, int64(maxBytes), fr.maxBytes)
}
func TestWithMiddleware(t *testing.T) {
m := make(map[string]string)
rt := router.NewRouter()
@@ -290,9 +297,9 @@ Port: 54321
}
for _, testCase := range testCases {
srv, err := NewServer(testCase.c, testCase.opts...)
svr, err := NewServer(testCase.c, testCase.opts...)
assert.Nil(t, err)
assert.Equal(t, srv.ngin.tlsConfig, testCase.res)
assert.Equal(t, svr.ngin.tlsConfig, testCase.res)
}
}
@@ -304,11 +311,11 @@ Port: 54321
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
rt := router.NewRouter()
srv, err := NewServer(cnf, WithRouter(rt))
svr, err := NewServer(cnf, WithRouter(rt))
assert.Nil(t, err)
opt := WithCors("local")
opt(srv)
opt(svr)
}
func TestWithCustomCors(t *testing.T) {
@@ -319,7 +326,7 @@ Port: 54321
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
rt := router.NewRouter()
srv, err := NewServer(cnf, WithRouter(rt))
svr, err := NewServer(cnf, WithRouter(rt))
assert.Nil(t, err)
opt := WithCustomCors(func(header http.Header) {
@@ -327,5 +334,5 @@ Port: 54321
}, func(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
}, "local")
opt(srv)
opt(svr)
}

View File

@@ -36,5 +36,6 @@ type (
jwt jwtSetting
signature signatureSetting
routes []Route
maxBytes int64
}
)

1
tools/goctl/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vscode

34
tools/goctl/Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
FROM golang:alpine AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0
ENV GOPROXY https://goproxy.cn,direct
RUN apk update --no-cache && apk add --no-cache tzdata
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
WORKDIR /build
ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o /app/goctl ./goctl.go
FROM alpine
RUN apk update --no-cache && apk add --no-cache protoc
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
COPY --from=builder /go/bin/protoc-gen-go /usr/bin/protoc-gen-go
COPY --from=builder /go/bin/protoc-gen-go-grpc /usr/bin/protoc-gen-go-grpc
ENV TZ Asia/Shanghai
WORKDIR /app
COPY --from=builder /app/goctl /app/goctl
CMD ["./goctl"]

View File

@@ -55,8 +55,9 @@ func ApiCommand(c *cli.Context) error {
home := c.String("home")
remote := c.String("remote")
branch := c.String("branch")
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote)
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}

View File

@@ -0,0 +1,40 @@
package dartgen
import (
"fmt"
"os"
"os/exec"
)
const dartExec = "dart"
func formatDir(dir string) error {
ok, err := dirctoryExists(dir)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("format failed, directory %q does not exist", dir)
}
_, err = exec.LookPath(dartExec)
if err != nil {
return err
}
cmd := exec.Command(dartExec, "format", dir)
cmd.Env = os.Environ()
cmd.Stderr = os.Stderr
return cmd.Run()
}
func dirctoryExists(dir string) (bool, error) {
_, err := os.Stat(dir)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

View File

@@ -2,6 +2,7 @@ package dartgen
import (
"errors"
"fmt"
"strings"
"github.com/urfave/cli"
@@ -13,12 +14,18 @@ import (
func DartCommand(c *cli.Context) error {
apiFile := c.String("api")
dir := c.String("dir")
isLegacy := c.Bool("legacy")
hostname := c.String("hostname")
if len(apiFile) == 0 {
return errors.New("missing -api")
}
if len(dir) == 0 {
return errors.New("missing -dir")
}
if len(hostname) == 0 {
fmt.Println("you could use '-hostname' flag to specify your server hostname")
hostname = "go-zero.dev"
}
api, err := parser.Parse(apiFile)
if err != nil {
@@ -30,8 +37,11 @@ func DartCommand(c *cli.Context) error {
dir = dir + "/"
}
api.Info.Title = strings.Replace(apiFile, ".api", "", -1)
logx.Must(genData(dir+"data/", api))
logx.Must(genApi(dir+"api/", api))
logx.Must(genVars(dir + "vars/"))
logx.Must(genData(dir+"data/", api, isLegacy))
logx.Must(genApi(dir+"api/", api, isLegacy))
logx.Must(genVars(dir+"vars/", isLegacy, hostname))
if err := formatDir(dir); err != nil {
logx.Errorf("failed to format, %v", err)
}
return nil
}

View File

@@ -2,13 +2,14 @@ package dartgen
import (
"os"
"strings"
"text/template"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
)
const apiTemplate = `import 'api.dart';
import '../data/{{with .Info}}{{.Title}}{{end}}.dart';
import '../data/{{with .Info}}{{getBaseName .Title}}{{end}}.dart';
{{with .Service}}
/// {{.Name}}
{{range .Routes}}
@@ -22,24 +23,45 @@ Future {{pathToFuncName .Path}}( {{if ne .Method "get"}}{{with .RequestType}}{{.
Function eventually}) async {
await api{{if eq .Method "get"}}Get{{else}}Post{{end}}('{{.Path}}',{{if ne .Method "get"}}request,{{end}}
ok: (data) {
if (ok != null) ok({{with .ResponseType}}{{.Name}}{{end}}.fromJson(data));
if (ok != null) ok({{with .ResponseType}}{{.Name}}.fromJson(data){{end}});
}, fail: fail, eventually: eventually);
}
{{end}}
{{end}}`
func genApi(dir string, api *spec.ApiSpec) error {
const apiTemplateV2 = `import 'api.dart';
import '../data/{{with .Info}}{{getBaseName .Title}}{{end}}.dart';
{{with .Service}}
/// {{.Name}}
{{range .Routes}}
/// --{{.Path}}--
///
/// request: {{with .RequestType}}{{.Name}}{{end}}
/// response: {{with .ResponseType}}{{.Name}}{{end}}
Future {{pathToFuncName .Path}}( {{if ne .Method "get"}}{{with .RequestType}}{{.Name}} request,{{end}}{{end}}
{Function({{with .ResponseType}}{{.Name}}{{end}})? ok,
Function(String)? fail,
Function? eventually}) async {
await api{{if eq .Method "get"}}Get{{else}}Post{{end}}('{{.Path}}',{{if ne .Method "get"}}request,{{end}}
ok: (data) {
if (ok != null) ok({{with .ResponseType}}{{.Name}}.fromJson(data){{end}});
}, fail: fail, eventually: eventually);
}
{{end}}
{{end}}`
func genApi(dir string, api *spec.ApiSpec, isLegacy bool) error {
err := os.MkdirAll(dir, 0o755)
if err != nil {
return err
}
err = genApiFile(dir)
err = genApiFile(dir, isLegacy)
if err != nil {
return err
}
file, err := os.OpenFile(dir+api.Service.Name+".dart", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
file, err := os.OpenFile(dir+strings.ToLower(api.Service.Name+".dart"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
@@ -47,7 +69,11 @@ func genApi(dir string, api *spec.ApiSpec) error {
defer file.Close()
t := template.New("apiTemplate")
t = t.Funcs(funcMap)
t, err = t.Parse(apiTemplate)
tpl := apiTemplateV2
if isLegacy {
tpl = apiTemplate
}
t, err = t.Parse(tpl)
if err != nil {
return err
}
@@ -55,7 +81,7 @@ func genApi(dir string, api *spec.ApiSpec) error {
return t.Execute(file, api)
}
func genApiFile(dir string) error {
func genApiFile(dir string, isLegacy bool) error {
path := dir + "api.dart"
if fileExists(path) {
return nil
@@ -66,6 +92,10 @@ func genApiFile(dir string) error {
}
defer apiFile.Close()
_, err = apiFile.WriteString(apiFileContent)
tpl := apiFileContentV2
if isLegacy {
tpl = apiFileContent
}
_, err = apiFile.WriteString(tpl)
return err
}

View File

@@ -2,6 +2,7 @@ package dartgen
import (
"os"
"strings"
"text/template"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
@@ -31,18 +32,42 @@ class {{.Name}}{
{{end}}
`
func genData(dir string, api *spec.ApiSpec) error {
const dataTemplateV2 = `// --{{with .Info}}{{.Title}}{{end}}--
{{ range .Types}}
class {{.Name}} {
{{range .Members}}
{{if .Comment}}{{.Comment}}{{end}}
final {{.Type.Name}} {{lowCamelCase .Name}};
{{end}}{{.Name}}({{if .Members}}{
{{range .Members}} required this.{{lowCamelCase .Name}},
{{end}}}{{end}});
factory {{.Name}}.fromJson(Map<String,dynamic> m) {
return {{.Name}}({{range .Members}}
{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{getPropertyFromMember .}}']
{{else if isClassListType .Type.Name}}(m['{{getPropertyFromMember .}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)).toList()
{{else}}{{.Type.Name}}.fromJson(m['{{getPropertyFromMember .}}']){{end}},{{end}}
);
}
Map<String,dynamic> toJson() {
return { {{range .Members}}
'{{getPropertyFromMember .}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
};
}
}
{{end}}`
func genData(dir string, api *spec.ApiSpec, isLegacy bool) error {
err := os.MkdirAll(dir, 0o755)
if err != nil {
return err
}
err = genTokens(dir)
err = genTokens(dir, isLegacy)
if err != nil {
return err
}
file, err := os.OpenFile(dir+api.Service.Name+".dart", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
file, err := os.OpenFile(dir+strings.ToLower(api.Service.Name+".dart"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
@@ -50,7 +75,11 @@ func genData(dir string, api *spec.ApiSpec) error {
t := template.New("dataTemplate")
t = t.Funcs(funcMap)
t, err = t.Parse(dataTemplate)
tpl := dataTemplateV2
if isLegacy {
tpl = dataTemplate
}
t, err = t.Parse(tpl)
if err != nil {
return err
}
@@ -63,7 +92,7 @@ func genData(dir string, api *spec.ApiSpec) error {
return t.Execute(file, api)
}
func genTokens(dir string) error {
func genTokens(dir string, isLeagcy bool) error {
path := dir + "tokens.dart"
if fileExists(path) {
return nil
@@ -75,7 +104,11 @@ func genTokens(dir string) error {
}
defer tokensFile.Close()
_, err = tokensFile.WriteString(tokensFileContent)
tpl := tokensFileContentV2
if isLeagcy {
tpl = tokensFileContent
}
_, err = tokensFile.WriteString(tpl)
return err
}

View File

@@ -1,11 +1,13 @@
package dartgen
import (
"fmt"
"io/ioutil"
"os"
)
const varTemplate = `import 'dart:convert';
const (
varTemplate = `import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../data/tokens.dart';
@@ -40,21 +42,59 @@ Future<Tokens> getTokens() async {
}
`
func genVars(dir string) error {
varTemplateV2 = `import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../data/tokens.dart';
const String _tokenKey = 'tokens';
/// Saves tokens
Future<bool> setTokens(Tokens tokens) async {
var sp = await SharedPreferences.getInstance();
return await sp.setString(_tokenKey, jsonEncode(tokens.toJson()));
}
/// remove tokens
Future<bool> removeTokens() async {
var sp = await SharedPreferences.getInstance();
return sp.remove(_tokenKey);
}
/// Reads tokens
Future<Tokens?> getTokens() async {
try {
var sp = await SharedPreferences.getInstance();
var str = sp.getString('tokens');
if (str.isEmpty) {
return null;
}
return Tokens.fromJson(jsonDecode(str));
} catch (e) {
print(e);
return null;
}
}`
)
func genVars(dir string, isLegacy bool, hostname string) error {
err := os.MkdirAll(dir, 0o755)
if err != nil {
return err
}
if !fileExists(dir + "vars.dart") {
err = ioutil.WriteFile(dir+"vars.dart", []byte(`const serverHost='demo-crm.xiaoheiban.cn';`), 0o644)
err = ioutil.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s';`, hostname)), 0o644)
if err != nil {
return err
}
}
if !fileExists(dir + "kv.dart") {
err = ioutil.WriteFile(dir+"kv.dart", []byte(varTemplate), 0o644)
tpl := varTemplateV2
if isLegacy {
tpl = varTemplate
}
err = ioutil.WriteFile(dir+"kv.dart", []byte(tpl), 0o644)
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"os"
"path"
"strings"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
@@ -34,6 +35,10 @@ func pathToFuncName(path string) string {
return util.ToLower(camel[:1]) + camel[1:]
}
func getBaseName(str string) string {
return path.Base(str)
}
func getPropertyFromMember(member spec.Member) string {
name, err := member.GetPropertyName()
if err != nil {
@@ -123,11 +128,10 @@ func specTypeToDart(tp spec.Type) (string, error) {
}
s := getBaseType(valueType)
if len(s) == 0 {
return s, errors.New("unsupported primitive type " + tp.Name())
if len(s) != 0 {
return s, nil
}
return s, nil
return fmt.Sprintf("List<%s>", valueType), nil
case spec.InterfaceType:
return "Object", nil
case spec.PointerType:

View File

@@ -37,3 +37,35 @@ func Test_getPropertyFromMember(t *testing.T) {
})
}
}
func Test_specTypeToDart(t *testing.T) {
tests := []struct {
name string
specType spec.Type
want string
wantErr bool
}{
{
name: "[]string should return List<String>",
specType: spec.ArrayType{RawName: "[]string", Value: spec.PrimitiveType{RawName: "string"}},
want: "List<String>",
},
{
name: "[]Foo should return List<Foo>",
specType: spec.ArrayType{RawName: "[]Foo", Value: spec.DefineStruct{RawName: "Foo"}},
want: "List<Foo>",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := specTypeToDart(tt.specType)
if (err != nil) != tt.wantErr {
t.Errorf("specTypeToDart() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("specTypeToDart() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -3,6 +3,7 @@ package dartgen
import "text/template"
var funcMap = template.FuncMap{
"getBaseName": getBaseName,
"getPropertyFromMember": getPropertyFromMember,
"isDirectType": isDirectType,
"isClassListType": isClassListType,
@@ -99,6 +100,96 @@ Future _apiRequest(String method, String path, dynamic data,
}
`
apiFileContentV2 = `import 'dart:io';
import 'dart:convert';
import '../vars/kv.dart';
import '../vars/vars.dart';
/// send request with post method
///
/// data: any request class that will be converted to json automatically
/// ok: is called when request succeeds
/// fail: is called when request fails
/// eventually: is always called until the nearby functions returns
Future apiPost(String path, dynamic data,
{Map<String, String>? header,
Function(Map<String, dynamic>)? ok,
Function(String)? fail,
Function? eventually}) async {
await _apiRequest('POST', path, data,
header: header, ok: ok, fail: fail, eventually: eventually);
}
/// send request with get method
///
/// ok: is called when request succeeds
/// fail: is called when request fails
/// eventually: is always called until the nearby functions returns
Future apiGet(String path,
{Map<String, String>? header,
Function(Map<String, dynamic>)? ok,
Function(String)? fail,
Function? eventually}) async {
await _apiRequest('GET', path, null,
header: header, ok: ok, fail: fail, eventually: eventually);
}
Future _apiRequest(String method, String path, dynamic data,
{Map<String, String>? header,
Function(Map<String, dynamic>)? ok,
Function(String)? fail,
Function? eventually}) async {
var tokens = await getTokens();
try {
var client = HttpClient();
HttpClientRequest r;
if (method == 'POST') {
r = await client.postUrl(Uri.parse('https://' + serverHost + path));
} else {
r = await client.getUrl(Uri.parse('https://' + serverHost + path));
}
r.headers.set('Content-Type', 'application/json');
if (tokens != null) {
r.headers.set('Authorization', tokens.accessToken);
}
if (header != null) {
header.forEach((k, v) {
r.headers.set(k, v);
});
}
var strData = '';
if (data != null) {
strData = jsonEncode(data);
}
r.write(strData);
var rp = await r.close();
var body = await rp.transform(utf8.decoder).join();
print('${rp.statusCode} - $path');
print('-- request --');
print(strData);
print('-- response --');
print('$body \n');
if (rp.statusCode == 404) {
if (fail != null) fail('404 not found');
} else {
Map<String, dynamic> base = jsonDecode(body);
if (rp.statusCode == 200) {
if (base['code'] != 0) {
if (fail != null) fail(base['desc']);
} else {
if (ok != null) ok(base['data']);
}
} else if (base['code'] != 0) {
if (fail != null) fail(base['desc']);
}
}
} catch (e) {
if (fail != null) fail(e.toString());
}
if (eventually != null) eventually();
}`
tokensFileContent = `class Tokens {
/// 用于访问的token, 每次请求都必须带在Header里面
final String accessToken;
@@ -132,5 +223,41 @@ Future _apiRequest(String method, String path, dynamic data,
};
}
}
`
tokensFileContentV2 = `class Tokens {
/// 用于访问的token, 每次请求都必须带在Header里面
final String accessToken;
final int accessExpire;
/// 用于刷新token
final String refreshToken;
final int refreshExpire;
final int refreshAfter;
Tokens({
required this.accessToken,
required this.accessExpire,
required this.refreshToken,
required this.refreshExpire,
required this.refreshAfter
});
factory Tokens.fromJson(Map<String, dynamic> m) {
return Tokens(
accessToken: m['access_token'],
accessExpire: m['access_expire'],
refreshToken: m['refresh_token'],
refreshExpire: m['refresh_expire'],
refreshAfter: m['refresh_after']);
}
Map<String, dynamic> toJson() {
return {
'access_token': accessToken,
'access_expire': accessExpire,
'refresh_token': refreshToken,
'refresh_expire': refreshExpire,
'refresh_after': refreshAfter,
};
}
}
`
)

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"go/format"
"go/scanner"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -28,14 +29,15 @@ const (
// GoFormatApi format api file
func GoFormatApi(c *cli.Context) error {
useStdin := c.Bool("stdin")
skipCheckDeclare := c.Bool("declare")
dir := c.String("dir")
var be errorx.BatchError
if useStdin {
if err := apiFormatByStdin(); err != nil {
if err := apiFormatReader(os.Stdin, dir, skipCheckDeclare); err != nil {
be.Add(err)
}
} else {
dir := c.String("dir")
if len(dir) == 0 {
return errors.New("missing -dir")
}
@@ -47,7 +49,7 @@ func GoFormatApi(c *cli.Context) error {
err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
if strings.HasSuffix(path, ".api") {
if err := ApiFormatByPath(path); err != nil {
if err := ApiFormatByPath(path, skipCheckDeclare); err != nil {
be.Add(util.WrapErr(err, fi.Name()))
}
}
@@ -64,13 +66,14 @@ func GoFormatApi(c *cli.Context) error {
return be.Err()
}
func apiFormatByStdin() error {
data, err := ioutil.ReadAll(os.Stdin)
// apiFormatReader
// filename is needed when there are `import` literals.
func apiFormatReader(reader io.Reader, filename string, skipCheckDeclare bool) error {
data, err := ioutil.ReadAll(reader)
if err != nil {
return err
}
result, err := apiFormat(string(data))
result, err := apiFormat(string(data), skipCheckDeclare, filename)
if err != nil {
return err
}
@@ -80,7 +83,7 @@ func apiFormatByStdin() error {
}
// ApiFormatByPath format api from file path
func ApiFormatByPath(apiFilePath string) error {
func ApiFormatByPath(apiFilePath string, skipCheckDeclare bool) error {
data, err := ioutil.ReadFile(apiFilePath)
if err != nil {
return err
@@ -91,12 +94,12 @@ func ApiFormatByPath(apiFilePath string) error {
return err
}
result, err := apiFormat(string(data), abs)
result, err := apiFormat(string(data), skipCheckDeclare, abs)
if err != nil {
return err
}
_, err = parser.ParseContent(result, abs)
_, err = parser.ParseContentWithParserSkipCheckTypeDeclaration(result, abs)
if err != nil {
return err
}
@@ -104,8 +107,13 @@ func ApiFormatByPath(apiFilePath string) error {
return ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm)
}
func apiFormat(data string, filename ...string) (string, error) {
_, err := parser.ParseContent(data, filename...)
func apiFormat(data string, skipCheckDeclare bool, filename ...string) (string, error) {
var err error
if skipCheckDeclare {
_, err = parser.ParseContentWithParserSkipCheckTypeDeclaration(data, filename...)
} else {
_, err = parser.ParseContent(data, filename...)
}
if err != nil {
return "", err
}

View File

@@ -1,18 +1,25 @@
package format
import (
"fmt"
"io/fs"
"io/ioutil"
"os"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
notFormattedStr = `
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string ` + "`" + `json:"message"` + "`" + `
Students []Student ` + "`" + `json:"students"` + "`" + `
}
service A-api {
@server(
@@ -26,7 +33,8 @@ handler: GreetHandler
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response {
Message string ` + "`" + `json:"message"` + "`" + `
Message string ` + "`" + `json:"message"` + "`" + `
Students []Student ` + "`" + `json:"students"` + "`" + `
}
service A-api {
@server(
@@ -37,7 +45,32 @@ service A-api {
)
func TestFormat(t *testing.T) {
r, err := apiFormat(notFormattedStr)
r, err := apiFormat(notFormattedStr, true)
assert.Nil(t, err)
assert.Equal(t, formattedStr, r)
_, err = apiFormat(notFormattedStr, false)
assert.Errorf(t, err, " line 7:13 can not found declaration 'Student' in context")
}
func Test_apiFormatReader_issue1721(t *testing.T) {
dir, err := os.MkdirTemp("", "goctl-api-format")
require.NoError(t, err)
defer os.RemoveAll(dir)
subDir := path.Join(dir, "sub")
err = os.MkdirAll(subDir, fs.ModePerm)
require.NoError(t, err)
importedFilename := path.Join(dir, "foo.api")
err = ioutil.WriteFile(importedFilename, []byte{}, fs.ModePerm)
require.NoError(t, err)
filename := path.Join(subDir, "bar.api")
err = ioutil.WriteFile(filename, []byte(fmt.Sprintf(`import "%s"`, importedFilename)), 0644)
require.NoError(t, err)
f, err := os.Open(filename)
require.NoError(t, err)
err = apiFormatReader(f, filename, false)
assert.NoError(t, err)
}

View File

@@ -33,8 +33,9 @@ func GoCommand(c *cli.Context) error {
namingStyle := c.String("style")
home := c.String("home")
remote := c.String("remote")
branch := c.String("branch")
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote)
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}
@@ -85,7 +86,7 @@ func DoGenProject(apiFile, dir, style string) error {
return err
}
if err := apiformat.ApiFormatByPath(apiFile); err != nil {
if err := apiformat.ApiFormatByPath(apiFile, false); err != nil {
return err
}

View File

@@ -7,6 +7,7 @@ import (
"sort"
"strings"
"text/template"
"time"
"github.com/zeromicro/go-zero/core/collection"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
@@ -23,7 +24,8 @@ const (
package handler
import (
"net/http"
"net/http"{{if .hasTimeout}}
"time"{{end}}
{{.importPackages}}
)
@@ -34,9 +36,10 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
`
routesAdditionTemplate = `
server.AddRoutes(
{{.routes}} {{.jwt}}{{.signature}} {{.prefix}}
{{.routes}} {{.jwt}}{{.signature}} {{.prefix}} {{.timeout}}
)
`
timeoutThreshold = time.Millisecond
)
var mapping = map[string]string{
@@ -57,6 +60,7 @@ type (
jwtEnabled bool
signatureEnabled bool
authName string
timeout string
middlewares []string
prefix string
jwtTrans string
@@ -80,6 +84,7 @@ func genRoutes(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error
return err
}
var hasTimeout bool
gt := template.Must(template.New("groupTemplate").Parse(templateText))
for _, g := range groups {
var gbuilder strings.Builder
@@ -110,6 +115,22 @@ func genRoutes(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error
rest.WithPrefix("%s"),`, g.prefix)
}
var timeout string
if len(g.timeout) > 0 {
duration, err := time.ParseDuration(g.timeout)
if err != nil {
return err
}
// why we check this, maybe some users set value 1, it's 1ns, not 1s.
if duration < timeoutThreshold {
return fmt.Errorf("timeout should not less than 1ms, now %v", duration)
}
timeout = fmt.Sprintf("rest.WithTimeout(%d * time.Millisecond),", duration/time.Millisecond)
hasTimeout = true
}
var routes string
if len(g.middlewares) > 0 {
gbuilder.WriteString("\n}...,")
@@ -130,6 +151,7 @@ rest.WithPrefix("%s"),`, g.prefix)
"jwt": jwt,
"signature": signature,
"prefix": prefix,
"timeout": timeout,
}); err != nil {
return err
}
@@ -139,8 +161,8 @@ rest.WithPrefix("%s"),`, g.prefix)
if err != nil {
return err
}
routeFilename = routeFilename + ".go"
routeFilename = routeFilename + ".go"
filename := path.Join(dir, handlerDir, routeFilename)
os.Remove(filename)
@@ -152,7 +174,8 @@ rest.WithPrefix("%s"),`, g.prefix)
category: category,
templateFile: routesTemplateFile,
builtinTemplate: routesTemplate,
data: map[string]string{
data: map[string]interface{}{
"hasTimeout": hasTimeout,
"importPackages": genRouteImports(rootPkg, api),
"routesAdditions": strings.TrimSpace(builder.String()),
},
@@ -171,7 +194,8 @@ func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
continue
}
}
importSet.AddStr(fmt.Sprintf("%s \"%s\"", toPrefix(folder), pathx.JoinPackages(parentPkg, handlerDir, folder)))
importSet.AddStr(fmt.Sprintf("%s \"%s\"", toPrefix(folder),
pathx.JoinPackages(parentPkg, handlerDir, folder)))
}
}
imports := importSet.KeysStr()
@@ -205,6 +229,8 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
})
}
groupedRoutes.timeout = g.GetAnnotation("timeout")
jwt := g.GetAnnotation("jwt")
if len(jwt) > 0 {
groupedRoutes.authName = jwt

View File

@@ -80,7 +80,7 @@ import com.google.gson.Gson
object {{with .Info}}{{.Title}}{{end}}{
{{range .Types}}
data class {{.Name}}({{$length := (len .Members)}}{{range $i,$item := .Members}}
val {{with $item}}{{lowCamelCase .Name}}: {{parseType .Type}}{{end}}{{if ne $i (add $length -1)}},{{end}}{{end}}
val {{with $item}}{{lowCamelCase .Name}}: {{parseType .Type.Name}}{{end}}{{if ne $i (add $length -1)}},{{end}}{{end}}
){{end}}
{{with .Service}}
{{range .Routes}}suspend fun {{routeToFuncName .Method .Path}}({{with .RequestType}}{{if ne .Name ""}}

View File

@@ -67,8 +67,9 @@ func CreateServiceCommand(c *cli.Context) error {
home := c.String("home")
remote := c.String("remote")
branch := c.String("branch")
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote)
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}

View File

@@ -19,7 +19,8 @@ type (
debug bool
log console.Console
antlr.DefaultErrorListener
src string
src string
skipCheckTypeDeclaration bool
}
// ParserOption defines an function with argument Parser
@@ -136,9 +137,11 @@ func (p *Parser) parse(filename, content string) (*Api, error) {
apiAstList = append(apiAstList, nestedApi)
}
err = p.checkTypeDeclaration(apiAstList)
if err != nil {
return nil, err
if !p.skipCheckTypeDeclaration {
err = p.checkTypeDeclaration(apiAstList)
if err != nil {
return nil, err
}
}
allApi := p.memberFill(apiAstList)
@@ -483,3 +486,9 @@ func WithParserPrefix(prefix string) ParserOption {
p.linePrefix = prefix
}
}
func WithParserSkipCheckTypeDeclaration() ParserOption {
return func(p *Parser) {
p.skipCheckTypeDeclaration = true
}
}

View File

@@ -648,19 +648,19 @@ func (s *TypeStruct) Equal(dt interface{}) bool {
return false
}
var expected, acual []*TypeField
var expected, actual []*TypeField
expected = append(expected, s.Fields...)
acual = append(acual, v.Fields...)
actual = append(actual, v.Fields...)
sort.Slice(expected, func(i, j int) bool {
return expected[i].DataType.Expr().Line() < expected[j].DataType.Expr().Line()
})
sort.Slice(acual, func(i, j int) bool {
return acual[i].DataType.Expr().Line() < acual[j].DataType.Expr().Line()
sort.Slice(actual, func(i, j int) bool {
return actual[i].DataType.Expr().Line() < actual[j].DataType.Expr().Line()
})
for index, each := range expected {
ac := acual[index]
ac := actual[index]
if !each.Equal(ac) {
return false
}

View File

@@ -12,7 +12,7 @@ import (
const (
versionRegex = `(?m)"v[1-9][0-9]*"`
importValueRegex = `(?m)"\/?(([a-zA-Z0-9.]+)+(\/?){1})+([a-zA-Z0-9]+)+\.api"`
importValueRegex = `(?m)"\/?(?:[^/]+\/)*[^/]+.api"`
tagRegex = `(?m)\x60[a-z]+:".+"\x60`
)

View File

@@ -21,8 +21,8 @@ func TestImportRegex(t *testing.T) {
{`"../foo/bar.api"`, true},
{`"../../foo/bar.api"`, true},
{`"bar..api"`, false},
{`"//bar.api"`, false},
{`"/foo/foo_bar.api"`, true},
}
for _, tt := range tests {
t.Run(tt.value, func(t *testing.T) {

View File

@@ -8,6 +8,7 @@ import (
"bufio"
"bytes"
"fmt"
"go/format"
"io/ioutil"
"log"
"os"
@@ -61,7 +62,12 @@ import "github.com/zeromicro/antlr"
}
}
err = ioutil.WriteFile(fp, buffer.Bytes(), os.ModePerm)
src, err := format.Source(buffer.Bytes())
if err != nil {
fmt.Printf("%+v\n", err)
break
}
err = ioutil.WriteFile(fp, src, os.ModePerm)
if err != nil {
fmt.Printf("%+v\n", err)
}

View File

@@ -33,9 +33,13 @@ func Parse(filename string) (*spec.ApiSpec, error) {
return spec, nil
}
// ParseContent parses the api content
func ParseContent(content string, filename ...string) (*spec.ApiSpec, error) {
astParser := ast.NewParser()
func parseContent(content string, skipCheckTypeDeclaration bool, filename ...string) (*spec.ApiSpec, error) {
var astParser *ast.Parser
if skipCheckTypeDeclaration {
astParser = ast.NewParser(ast.WithParserSkipCheckTypeDeclaration())
} else {
astParser = ast.NewParser()
}
ast, err := astParser.ParseContent(content, filename...)
if err != nil {
return nil, err
@@ -51,6 +55,16 @@ func ParseContent(content string, filename ...string) (*spec.ApiSpec, error) {
return spec, nil
}
// ParseContent parses the api content
func ParseContent(content string, filename ...string) (*spec.ApiSpec, error) {
return parseContent(content, false, filename...)
}
// ParseContentWithParserSkipCheckTypeDeclaration parses the api content with skip check type declaration
func ParseContentWithParserSkipCheckTypeDeclaration(content string, filename ...string) (*spec.ApiSpec, error) {
return parseContent(content, true, filename...)
}
func (p parser) convert2Spec() error {
p.fillInfo()
p.fillSyntax()

View File

@@ -557,7 +557,7 @@ service foo-api{
## 隐藏通道
隐藏通道目前主要为空符号,换行符号以及注释,这里我们只说注释,因为空白符号和换行符号我们目前拿来也无用。
隐藏通道目前主要为空符号,换行符号以及注释,这里我们只说注释,因为空白符号和换行符号我们目前拿来也无用。
### 单行注释
@@ -679,4 +679,4 @@ service foo-api{
*/
post /foo (Foo) returns (Foo) // route comment
}
```
```

View File

@@ -48,17 +48,15 @@ func Completion(c *cli.Context) error {
flag := magic
err = ioutil.WriteFile(zshF, zsh, os.ModePerm)
if err != nil {
return err
if err == nil {
flag |= flagZsh
}
flag |= flagZsh
err = ioutil.WriteFile(bashF, bash, os.ModePerm)
if err != nil {
return err
if err == nil {
flag |= flagBash
}
flag |= flagBash
buffer.WriteString(aurora.BrightGreen("generation auto completion success!\n").String())
buffer.WriteString(aurora.BrightGreen("executes the following script to setting shell:\n").String())
switch flag {

View File

@@ -24,14 +24,17 @@ const (
// Docker describes a dockerfile
type Docker struct {
Chinese bool
GoRelPath string
GoFile string
ExeFile string
HasPort bool
Port int
Argument string
Version string
Chinese bool
GoRelPath string
GoFile string
ExeFile string
BaseImage string
HasPort bool
Port int
Argument string
Version string
HasTimezone bool
Timezone string
}
// DockerCommand provides the entry for goctl docker
@@ -46,8 +49,10 @@ func DockerCommand(c *cli.Context) (err error) {
home := c.String("home")
version := c.String("version")
remote := c.String("remote")
branch := c.String("branch")
timezone := c.String("tz")
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote)
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}
@@ -69,9 +74,10 @@ func DockerCommand(c *cli.Context) (err error) {
return fmt.Errorf("file %q not found", goFile)
}
base := c.String("base")
port := c.Int("port")
if _, err := os.Stat(etcDir); os.IsNotExist(err) {
return generateDockerfile(goFile, port, version)
return generateDockerfile(goFile, base, port, version, timezone)
}
cfg, err := findConfig(goFile, etcDir)
@@ -79,7 +85,7 @@ func DockerCommand(c *cli.Context) (err error) {
return err
}
if err := generateDockerfile(goFile, port, version, "-f", "etc/"+cfg); err != nil {
if err := generateDockerfile(goFile, base, port, version, timezone, "-f", "etc/"+cfg); err != nil {
return err
}
@@ -120,7 +126,7 @@ func findConfig(file, dir string) (string, error) {
return files[0], nil
}
func generateDockerfile(goFile string, port int, version string, args ...string) error {
func generateDockerfile(goFile, base string, port int, version, timezone string, args ...string) error {
projPath, err := getFilePath(filepath.Dir(goFile))
if err != nil {
return err
@@ -149,14 +155,17 @@ func generateDockerfile(goFile string, port int, version string, args ...string)
_, offset := time.Now().Zone()
t := template.Must(template.New("dockerfile").Parse(text))
return t.Execute(out, Docker{
Chinese: offset == cstOffset,
GoRelPath: projPath,
GoFile: goFile,
ExeFile: pathx.FileNameWithoutExt(filepath.Base(goFile)),
HasPort: port > 0,
Port: port,
Argument: builder.String(),
Version: version,
Chinese: offset == cstOffset,
GoRelPath: projPath,
GoFile: goFile,
ExeFile: pathx.FileNameWithoutExt(filepath.Base(goFile)),
BaseImage: base,
HasPort: port > 0,
Port: port,
Argument: builder.String(),
Version: version,
HasTimezone: len(timezone) > 0,
Timezone: timezone,
})
}

View File

@@ -13,10 +13,11 @@ const (
LABEL stage=gobuilder
ENV CGO_ENABLED 0
ENV GOOS linux
{{if .Chinese}}ENV GOPROXY https://goproxy.cn,direct
{{end}}{{if .HasTimezone}}
RUN apk update --no-cache && apk add --no-cache tzdata
{{end}}
WORKDIR /build/zero
WORKDIR /build
ADD go.mod .
ADD go.sum .
@@ -26,11 +27,12 @@ COPY . .
{{end}}RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoRelPath}}/{{.GoFile}}
FROM alpine
RUN apk update --no-cache && apk add --no-cache ca-certificates tzdata
ENV TZ Asia/Shanghai
FROM {{.BaseImage}}
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
{{if .HasTimezone}}COPY --from=builder /usr/share/zoneinfo/{{.Timezone}} /usr/share/zoneinfo/{{.Timezone}}
ENV TZ {{.Timezone}}
{{end}}
WORKDIR /app
COPY --from=builder /app/{{.ExeFile}} /app/{{.ExeFile}}{{if .Argument}}
COPY --from=builder /app/etc /app/etc{{end}}

View File

@@ -40,51 +40,53 @@ var bins = []bin{
func Check(ctx *cli.Context) error {
install := ctx.Bool("install")
force := ctx.Bool("force")
return check(install, force)
verbose := ctx.Bool("verbose")
return Prepare(install, force, verbose)
}
func check(install, force bool) error {
func Prepare(install, force, verbose bool) error {
log := console.NewColorConsole(verbose)
pending := true
console.Info("[goctl-env]: preparing to check env")
log.Info("[goctl-env]: preparing to check env")
defer func() {
if p := recover(); p != nil {
console.Error("%+v", p)
log.Error("%+v", p)
return
}
if pending {
console.Success("\n[goctl-env]: congratulations! your goctl environment is ready!")
log.Success("\n[goctl-env]: congratulations! your goctl environment is ready!")
} else {
console.Error(`
log.Error(`
[goctl-env]: check env finish, some dependencies is not found in PATH, you can execute
command 'goctl env check --install' or 'goctl env install' to install it, for details,
please see 'goctl env check --help' or 'goctl env install --help'`)
command 'goctl env check --install' to install it, for details, please execute command
'goctl env check --help'`)
}
}()
for _, e := range bins {
time.Sleep(200 * time.Millisecond)
console.Info("")
console.Info("[goctl-env]: looking up %q", e.name)
log.Info("")
log.Info("[goctl-env]: looking up %q", e.name)
if e.exists {
console.Success("[goctl-env]: %q is installed", e.name)
log.Success("[goctl-env]: %q is installed", e.name)
continue
}
console.Warning("[goctl-env]: %q is not found in PATH", e.name)
log.Warning("[goctl-env]: %q is not found in PATH", e.name)
if install {
install := func() {
console.Info("[goctl-env]: preparing to install %q", e.name)
log.Info("[goctl-env]: preparing to install %q", e.name)
path, err := e.get(env.Get(env.GoctlCache))
if err != nil {
console.Error("[goctl-env]: an error interrupted the installation: %+v", err)
log.Error("[goctl-env]: an error interrupted the installation: %+v", err)
pending = false
} else {
console.Success("[goctl-env]: %q is already installed in %q", e.name, path)
log.Success("[goctl-env]: %q is already installed in %q", e.name, path)
}
}
if force {
install()
continue
}
console.Info("[goctl-env]: do you want to install %q [y: YES, n: No]", e.name)
log.Info("[goctl-env]: do you want to install %q [y: YES, n: No]", e.name)
for {
var in string
fmt.Scanln(&in)
@@ -95,10 +97,10 @@ please see 'goctl env check --help' or 'goctl env install --help'`)
brk = true
case strings.EqualFold(in, "n"):
pending = false
console.Info("[goctl-env]: %q installation is ignored", e.name)
log.Info("[goctl-env]: %q installation is ignored", e.name)
brk = true
default:
console.Error("[goctl-env]: invalid input, input 'y' for yes, 'n' for no")
log.Error("[goctl-env]: invalid input, input 'y' for yes, 'n' for no")
}
if brk {
break

View File

@@ -13,5 +13,5 @@ require (
github.com/urfave/cli v1.22.5
github.com/zeromicro/antlr v0.0.1
github.com/zeromicro/ddl-parser v1.0.3
github.com/zeromicro/go-zero v1.3.0
github.com/zeromicro/go-zero v1.3.1
)

View File

@@ -36,7 +36,6 @@ github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHg
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
@@ -54,13 +53,15 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec h1:EEyRvzmpEUZ+I8WmD5cw/vY8EqhambkOqy5iFr0908A=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -69,7 +70,6 @@ github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
@@ -82,6 +82,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -97,7 +99,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -138,8 +139,8 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -327,7 +328,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -358,11 +358,11 @@ github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk
github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
github.com/zeromicro/ddl-parser v1.0.3 h1:hFecpbt0oPQMhHAbqG1tz78MUepHUnOkFJp1dvRBFyc=
github.com/zeromicro/ddl-parser v1.0.3/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
github.com/zeromicro/go-zero v1.3.0 h1:Eyn36yBtR043sm4YKmxR6eS3UA/GtZDktQ+UqIJ3Lm0=
github.com/zeromicro/go-zero v1.3.0/go.mod h1:Hy4o1VFAt32lXaQMbaBhoFeZjA/rJqJ4PTGNdGsURcc=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
github.com/zeromicro/go-zero v1.3.1 h1:uVkELq9kosgRZBSERb+eG7+oY2E+BEpOJW5vZZ354Cs=
github.com/zeromicro/go-zero v1.3.1/go.mod h1:JsgCzJSUcjZl487xtqWHzYFa7Wl4f5Gi3lcteOWgNRA=
go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -377,11 +377,15 @@ go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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=
@@ -455,8 +459,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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=
@@ -516,9 +520,11 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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=
@@ -610,7 +616,7 @@ google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
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=
@@ -622,9 +628,8 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
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=

View File

@@ -23,7 +23,6 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/completion"
"github.com/zeromicro/go-zero/tools/goctl/docker"
"github.com/zeromicro/go-zero/tools/goctl/env"
"github.com/zeromicro/go-zero/tools/goctl/internal/errorx"
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
"github.com/zeromicro/go-zero/tools/goctl/kube"
"github.com/zeromicro/go-zero/tools/goctl/migrate"
@@ -70,6 +69,10 @@ var commands = []cli.Command{
Name: "force, f",
Usage: "silent installation of non-existent dependencies",
},
cli.BoolFlag{
Name: "verbose, v",
Usage: "enable log output",
},
},
Action: env.Check,
},
@@ -111,6 +114,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
},
Action: apigen.ApiCommand,
Subcommands: []cli.Command{
@@ -130,6 +137,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
cli.StringFlag{
Name: "style",
Usage: "the file naming format, see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md]",
@@ -152,6 +163,10 @@ var commands = []cli.Command{
Name: "stdin",
Usage: "use stdin to input api doc content, press \"ctrl + d\" to send EOF",
},
cli.BoolFlag{
Name: "declare",
Usage: "use to skip check api types already declare",
},
},
Action: format.GoFormatApi,
},
@@ -209,6 +224,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
},
Action: gogen.GoCommand,
},
@@ -266,6 +285,14 @@ var commands = []cli.Command{
Name: "api",
Usage: "the api file",
},
cli.BoolFlag{
Name: "legacy",
Usage: "legacy generator for flutter v1",
},
cli.StringFlag{
Name: "hostname",
Usage: "hostname of the server",
},
},
Action: dartgen.DartCommand,
},
@@ -321,6 +348,11 @@ var commands = []cli.Command{
Name: "go",
Usage: "the file that contains main function",
},
cli.StringFlag{
Name: "base",
Usage: "the base image to build the docker image, default scratch",
Value: "scratch",
},
cli.IntFlag{
Name: "port",
Usage: "the port to expose, default none",
@@ -337,10 +369,19 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
cli.StringFlag{
Name: "version",
Usage: "the goctl builder golang image version",
},
cli.StringFlag{
Name: "tz",
Usage: "the timezone of the container",
Value: "Asia/Shanghai",
},
},
Action: docker.DockerCommand,
},
@@ -437,6 +478,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
cli.StringFlag{
Name: "serviceAccount",
Usage: "the ServiceAccount for the deployment",
@@ -451,9 +496,8 @@ var commands = []cli.Command{
Usage: "generate rpc code",
Subcommands: []cli.Command{
{
Name: "new",
Usage: `generate rpc demo service`,
Description: aurora.Yellow(`deprecated: zrpc code generation use "goctl rpc protoc" instead, for the details see "goctl rpc protoc --help"`).String(),
Name: "new",
Usage: `generate rpc demo service`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "style",
@@ -474,6 +518,14 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
cli.BoolFlag{
Name: "verbose, v",
Usage: "enable log output",
},
},
Action: rpc.RPCNew,
},
@@ -496,6 +548,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
},
Action: rpc.RPCTemplate,
},
@@ -506,22 +562,30 @@ var commands = []cli.Command{
Description: "for details, see https://go-zero.dev/cn/goctl-rpc.html",
Action: rpc.ZRPC,
Flags: []cli.Flag{
cli.StringFlag{
cli.StringSliceFlag{
Name: "go_out",
Hidden: true,
},
cli.StringFlag{
cli.StringSliceFlag{
Name: "go-grpc_out",
Hidden: true,
},
cli.StringFlag{
cli.StringSliceFlag{
Name: "go_opt",
Hidden: true,
},
cli.StringFlag{
cli.StringSliceFlag{
Name: "go-grpc_opt",
Hidden: true,
},
cli.StringSliceFlag{
Name: "plugin",
Hidden: true,
},
cli.StringSliceFlag{
Name: "proto_path,I",
Hidden: true,
},
cli.StringFlag{
Name: "zrpc_out",
Usage: "the zrpc output directory",
@@ -540,50 +604,15 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
},
},
{
Name: "proto",
Usage: `generate rpc from proto`,
Description: aurora.Yellow(`deprecated: zrpc code generation use "goctl rpc protoc" instead, for the details see "goctl rpc protoc --help"`).String(),
Flags: []cli.Flag{
cli.StringFlag{
Name: "src, s",
Usage: "the file path of the proto source file",
},
cli.StringSliceFlag{
Name: "proto_path, I",
Usage: `native command of protoc, specify the directory in which to search for imports. [optional]`,
},
cli.StringSliceFlag{
Name: "go_opt",
Usage: `native command of protoc-gen-go, specify the mapping from proto to go, eg --go_opt=proto_import=go_package_import. [optional]`,
},
cli.StringFlag{
Name: "dir, d",
Usage: `the target path of the code`,
},
cli.StringFlag{
Name: "style",
Usage: "the file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]",
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
cli.BoolFlag{
Name: "idea",
Usage: "whether the command execution environment is from idea plugin. [optional]",
},
cli.StringFlag{
Name: "home",
Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
"if they are, --remote has higher priority",
},
cli.StringFlag{
Name: "remote",
Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
Name: "verbose, v",
Usage: "enable log output",
},
},
Action: rpc.RPC,
},
},
},
@@ -634,6 +663,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
},
Action: model.MysqlDDL,
},
@@ -676,6 +709,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
},
Action: model.MySqlDataSource,
},
@@ -728,6 +765,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
},
Action: model.PostgreSqlDataSource,
},
@@ -764,6 +805,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "branch",
Usage: "the branch of the remote repo, it does work with --remote",
},
},
Action: mongo.Action,
},
@@ -860,7 +905,7 @@ func main() {
// cli already print error messages.
if err := app.Run(os.Args); err != nil {
fmt.Println(aurora.Red(errorx.Wrap(err).Error()))
fmt.Println(aurora.Red(err.Error()))
os.Exit(codeFailure)
}
}

View File

@@ -6,7 +6,7 @@ import (
)
// BuildVersion is the version of goctl.
const BuildVersion = "1.3.2"
const BuildVersion = "1.3.4"
var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5}

View File

@@ -44,8 +44,9 @@ func DeploymentCommand(c *cli.Context) error {
nodePort := c.Int("nodePort")
home := c.String("home")
remote := c.String("remote")
branch := c.String("branch")
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote)
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}

View File

@@ -20,8 +20,9 @@ func Action(ctx *cli.Context) error {
s := ctx.String("style")
home := ctx.String("home")
remote := ctx.String("remote")
branch := ctx.String("branch")
if len(remote) > 0 {
repo, _ := file.CloneIntoGitHome(remote)
repo, _ := file.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}

View File

@@ -30,6 +30,8 @@ const (
flagDatabase = "database"
flagSchema = "schema"
flagHome = "home"
flagRemote = "remote"
flagBranch = "branch"
)
var errNotMatched = errors.New("sql not matched")
@@ -43,9 +45,10 @@ func MysqlDDL(ctx *cli.Context) error {
style := ctx.String(flagStyle)
database := ctx.String(flagDatabase)
home := ctx.String(flagHome)
remote := ctx.String("remote")
remote := ctx.String(flagRemote)
branch := ctx.String(flagBranch)
if len(remote) > 0 {
repo, _ := file.CloneIntoGitHome(remote)
repo, _ := file.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}
@@ -68,10 +71,11 @@ func MySqlDataSource(ctx *cli.Context) error {
cache := ctx.Bool(flagCache)
idea := ctx.Bool(flagIdea)
style := ctx.String(flagStyle)
home := ctx.String("home")
remote := ctx.String("remote")
home := ctx.String(flagHome)
remote := ctx.String(flagRemote)
branch := ctx.String(flagBranch)
if len(remote) > 0 {
repo, _ := file.CloneIntoGitHome(remote)
repo, _ := file.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}
@@ -97,10 +101,11 @@ func PostgreSqlDataSource(ctx *cli.Context) error {
idea := ctx.Bool(flagIdea)
style := ctx.String(flagStyle)
schema := ctx.String(flagSchema)
home := ctx.String("home")
remote := ctx.String("remote")
home := ctx.String(flagHome)
remote := ctx.String(flagRemote)
branch := ctx.String(flagBranch)
if len(remote) > 0 {
repo, _ := file.CloneIntoGitHome(remote)
repo, _ := file.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}

View File

@@ -20,16 +20,13 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
)
const (
pwd = "."
createTableFlag = `(?m)^(?i)CREATE\s+TABLE` // ignore case
)
const pwd = "."
type (
defaultGenerator struct {
// source string
dir string
console.Console
// source string
dir string
pkg string
cfg *config.Config
isPostgreSql bool
@@ -48,6 +45,12 @@ type (
updateCode string
deleteCode string
cacheExtra string
tableName string
}
codeTuple struct {
modelCode string
modelCustomCode string
}
)
@@ -109,7 +112,7 @@ func (g *defaultGenerator) StartFromDDL(filename string, withCache bool, databas
}
func (g *defaultGenerator) StartFromInformationSchema(tables map[string]*model.Table, withCache bool) error {
m := make(map[string]string)
m := make(map[string]*codeTuple)
for _, each := range tables {
table, err := parser.ConvertDataType(each)
if err != nil {
@@ -120,14 +123,21 @@ func (g *defaultGenerator) StartFromInformationSchema(tables map[string]*model.T
if err != nil {
return err
}
customCode, err := g.genModelCustom(*table, withCache)
if err != nil {
return err
}
m[table.Name.Source()] = code
m[table.Name.Source()] = &codeTuple{
modelCode: code,
modelCustomCode: customCode,
}
}
return g.createFile(m)
}
func (g *defaultGenerator) createFile(modelList map[string]string) error {
func (g *defaultGenerator) createFile(modelList map[string]*codeTuple) error {
dirAbs, err := filepath.Abs(g.dir)
if err != nil {
return err
@@ -140,20 +150,28 @@ func (g *defaultGenerator) createFile(modelList map[string]string) error {
return err
}
for tableName, code := range modelList {
for tableName, codes := range modelList {
tn := stringx.From(tableName)
modelFilename, err := format.FileNamingFormat(g.cfg.NamingFormat, fmt.Sprintf("%s_model", tn.Source()))
modelFilename, err := format.FileNamingFormat(g.cfg.NamingFormat,
fmt.Sprintf("%s_model", tn.Source()))
if err != nil {
return err
}
name := util.SafeString(modelFilename) + ".go"
name := util.SafeString(modelFilename) + "_gen.go"
filename := filepath.Join(dirAbs, name)
err = ioutil.WriteFile(filename, []byte(codes.modelCode), os.ModePerm)
if err != nil {
return err
}
name = util.SafeString(modelFilename) + ".go"
filename = filepath.Join(dirAbs, name)
if pathx.FileExists(filename) {
g.Warning("%s already exists, ignored.", name)
continue
}
err = ioutil.WriteFile(filename, []byte(code), os.ModePerm)
err = ioutil.WriteFile(filename, []byte(codes.modelCustomCode), os.ModePerm)
if err != nil {
return err
}
@@ -183,8 +201,9 @@ func (g *defaultGenerator) createFile(modelList map[string]string) error {
}
// ret1: key-table name,value-code
func (g *defaultGenerator) genFromDDL(filename string, withCache bool, database string) (map[string]string, error) {
m := make(map[string]string)
func (g *defaultGenerator) genFromDDL(filename string, withCache bool, database string) (
map[string]*codeTuple, error) {
m := make(map[string]*codeTuple)
tables, err := parser.Parse(filename, database)
if err != nil {
return nil, err
@@ -195,8 +214,15 @@ func (g *defaultGenerator) genFromDDL(filename string, withCache bool, database
if err != nil {
return nil, err
}
customCode, err := g.genModelCustom(*e, withCache)
if err != nil {
return nil, err
}
m[e.Name.Source()] = code
m[e.Name.Source()] = &codeTuple{
modelCode: code,
modelCustomCode: customCode,
}
}
return m, nil
@@ -223,7 +249,7 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
table.UniqueCacheKey = uniqueKey
table.ContainsUniqueCacheKey = len(uniqueKey) > 0
importsCode, err := genImports(withCache, in.ContainsTime(), table)
importsCode, err := genImports(table, withCache, in.ContainsTime())
if err != nil {
return "", err
}
@@ -261,7 +287,8 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
}
var list []string
list = append(list, insertCodeMethod, findOneCodeMethod, ret.findOneInterfaceMethod, updateCodeMethod, deleteCodeMethod)
list = append(list, insertCodeMethod, findOneCodeMethod, ret.findOneInterfaceMethod,
updateCodeMethod, deleteCodeMethod)
typesCode, err := genTypes(table, strings.Join(modelutil.TrimStringSlice(list), pathx.NL), withCache)
if err != nil {
return "", err
@@ -272,6 +299,11 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
return "", err
}
tableName, err := genTableName(table)
if err != nil {
return "", err
}
code := &code{
importsCode: importsCode,
varsCode: varsCode,
@@ -282,6 +314,7 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
updateCode: updateCode,
deleteCode: deleteCode,
cacheExtra: ret.cacheExtra,
tableName: tableName,
}
output, err := g.executeModel(table, code)
@@ -292,8 +325,30 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
return output.String(), nil
}
func (g *defaultGenerator) genModelCustom(in parser.Table, withCache bool) (string, error) {
text, err := pathx.LoadTemplate(category, modelCustomTemplateFile, template.ModelCustom)
if err != nil {
return "", err
}
t := util.With("model-custom").
Parse(text).
GoFmt(true)
output, err := t.Execute(map[string]interface{}{
"pkg": g.pkg,
"withCache": withCache,
"upperStartCamelObject": in.Name.ToCamel(),
"lowerStartCamelObject": stringx.From(in.Name.ToCamel()).Untitle(),
})
if err != nil {
return "", err
}
return output.String(), nil
}
func (g *defaultGenerator) executeModel(table Table, code *code) (*bytes.Buffer, error) {
text, err := pathx.LoadTemplate(category, modelTemplateFile, template.Model)
text, err := pathx.LoadTemplate(category, modelGenTemplateFile, template.ModelGen)
if err != nil {
return nil, err
}
@@ -311,6 +366,7 @@ func (g *defaultGenerator) executeModel(table Table, code *code) (*bytes.Buffer,
"update": code.updateCode,
"delete": code.deleteCode,
"extraMethod": code.cacheExtra,
"tableName": code.tableName,
"data": table,
})
if err != nil {

View File

@@ -4,16 +4,19 @@ import (
"database/sql"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stringx"
"github.com/zeromicro/go-zero/tools/goctl/config"
"github.com/zeromicro/go-zero/tools/goctl/model/sql/builderx"
"github.com/zeromicro/go-zero/tools/goctl/model/sql/parser"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
@@ -121,3 +124,31 @@ func TestFields(t *testing.T) {
assert.Equal(t, "`name`,`age`,`score`", studentRowsExpectAutoSet)
assert.Equal(t, "`name`=?,`age`=?,`score`=?", studentRowsWithPlaceHolder)
}
func Test_genPublicModel(t *testing.T) {
var err error
dir := pathx.MustTempDir()
modelDir := path.Join(dir, "model")
err = os.MkdirAll(modelDir, 0777)
require.NoError(t, err)
defer os.RemoveAll(dir)
modelFilename := filepath.Join(modelDir, "foo.sql")
err = ioutil.WriteFile(modelFilename, []byte(source), 0777)
require.NoError(t, err)
g, err := NewDefaultGenerator(modelDir, &config.Config{
NamingFormat: config.DefaultFormat,
})
require.NoError(t, err)
tables, err := parser.Parse(modelFilename, "")
require.Equal(t, 1, len(tables))
code, err := g.genModelCustom(*tables[0], false)
assert.NoError(t, err)
assert.True(t, strings.Contains(code, "package model"))
assert.True(t, strings.Contains(code, "TestUserModel interface {\n\t\ttestUserModel\n\t}\n"))
assert.True(t, strings.Contains(code, "customTestUserModel struct {\n\t\t*defaultTestUserModel\n\t}\n"))
assert.True(t, strings.Contains(code, "func NewTestUserModel(conn sqlx.SqlConn) TestUserModel {"))
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
func genImports(withCache, timeImport bool, table Table) (string, error) {
func genImports(table Table, withCache, timeImport bool) (string, error) {
if withCache {
text, err := pathx.LoadTemplate(category, importsTemplateFile, template.Imports)
if err != nil {

View File

@@ -55,7 +55,6 @@ func genInsert(table Table, withCache, postgreSql bool) (string, string, error)
Parse(text).
Execute(map[string]interface{}{
"withCache": withCache,
"containsIndexCache": table.ContainsUniqueCacheKey,
"upperStartCamelObject": camel,
"lowerStartCamelObject": stringx.From(camel).Untitle(),
"expression": strings.Join(expressions, ", "),

View File

@@ -0,0 +1,26 @@
package gen
import (
"github.com/zeromicro/go-zero/tools/goctl/model/sql/template"
"github.com/zeromicro/go-zero/tools/goctl/util"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
func genTableName(table Table) (string, error) {
text, err := pathx.LoadTemplate(category, tableNameTemplateFile, template.TableName)
if err != nil {
return "", err
}
output, err := util.With("tableName").
Parse(text).
Execute(map[string]interface{}{
"tableName": table.Name.Source(),
"upperStartCamelObject": table.Name.ToCamel(),
})
if err != nil {
return "", nil
}
return output.String(), nil
}

View File

@@ -22,8 +22,10 @@ const (
importsWithNoCacheTemplateFile = "import-no-cache.tpl"
insertTemplateFile = "insert.tpl"
insertTemplateMethodFile = "interface-insert.tpl"
modelTemplateFile = "model.tpl"
modelGenTemplateFile = "model-gen.tpl"
modelCustomTemplateFile = "model.tpl"
modelNewTemplateFile = "model-new.tpl"
tableNameTemplateFile = "table-name.tpl"
tagTemplateFile = "tag.tpl"
typesTemplateFile = "types.tpl"
updateTemplateFile = "update.tpl"
@@ -45,8 +47,10 @@ var templates = map[string]string{
importsWithNoCacheTemplateFile: template.ImportsNoCache,
insertTemplateFile: template.Insert,
insertTemplateMethodFile: template.InsertMethod,
modelTemplateFile: template.Model,
modelGenTemplateFile: template.ModelGen,
modelCustomTemplateFile: template.ModelCustom,
modelNewTemplateFile: template.New,
tableNameTemplateFile: template.TableName,
tagTemplateFile: template.Tag,
typesTemplateFile: template.Types,
updateTemplateFile: template.Update,
@@ -70,7 +74,7 @@ func GenTemplates(_ *cli.Context) error {
return pathx.InitTemplates(category, templates)
}
// RevertTemplate recovers the delete template files
// RevertTemplate reverts the deleted template files
func RevertTemplate(name string) error {
content, ok := templates[name]
if !ok {

View File

@@ -4,6 +4,7 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/model/sql/template"
"github.com/zeromicro/go-zero/tools/goctl/util"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
)
func genTypes(table Table, methods string, withCache bool) (string, error) {
@@ -24,6 +25,7 @@ func genTypes(table Table, methods string, withCache bool) (string, error) {
"withCache": withCache,
"method": methods,
"upperStartCamelObject": table.Name.ToCamel(),
"lowerStartCamelObject": stringx.From(table.Name.ToCamel()).Untitle(),
"fields": fieldsString,
"data": table,
})

View File

@@ -1,9 +1,10 @@
package template
// Delete defines a delete template
var Delete = `
const (
// Delete defines a delete template
Delete = `
func (m *default{{.upperStartCamelObject}}Model) Delete(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
{{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOneCtx(ctx, {{.lowerStartCamelPrimaryKey}})
{{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne(ctx, {{.lowerStartCamelPrimaryKey}})
if err!=nil{
return err
}
@@ -18,5 +19,6 @@ func (m *default{{.upperStartCamelObject}}Model) Delete(ctx context.Context, {{.
}
`
// DeleteMethod defines a delete template for interface method
var DeleteMethod = `Delete(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error`
// DeleteMethod defines a delete template for interface method
DeleteMethod = `Delete(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error`
)

View File

@@ -1,7 +1,7 @@
package template
// Error defines an error template
var Error = `package {{.pkg}}
const Error = `package {{.pkg}}
import "github.com/zeromicro/go-zero/core/stores/sqlx"

View File

@@ -1,4 +1,4 @@
package template
// Field defines a filed template for types
var Field = `{{.name}} {{.type}} {{.tag}} {{if .hasComment}}// {{.comment}}{{end}}`
const Field = `{{.name}} {{.type}} {{.tag}} {{if .hasComment}}// {{.comment}}{{end}}`

View File

@@ -1,7 +1,8 @@
package template
// FindOne defines find row by id.
var FindOne = `
const (
// FindOne defines find row by id.
FindOne = `
func (m *default{{.upperStartCamelObject}}Model) FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) {
{{if .withCache}}{{.cacheKey}}
var resp {{.upperStartCamelObject}}
@@ -30,9 +31,9 @@ func (m *default{{.upperStartCamelObject}}Model) FindOne(ctx context.Context, {{
}
`
// FindOneByField defines find row by field.
var FindOneByField = `
func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{.upperStartCamelObject}}, error) {
// FindOneByField defines find row by field.
FindOneByField = `
func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) {
{{if .withCache}}{{.cacheKey}}
var resp {{.upperStartCamelObject}}
err := m.QueryRowIndexCtx(ctx, &resp, {{.cacheKeyVariable}}, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
@@ -64,8 +65,8 @@ func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}
}{{end}}
`
// FindOneByFieldExtraMethod defines find row by field with extras.
var FindOneByFieldExtraMethod = `
// FindOneByFieldExtraMethod defines find row by field with extras.
FindOneByFieldExtraMethod = `
func (m *default{{.upperStartCamelObject}}Model) formatPrimary(primary interface{}) string {
return fmt.Sprintf("%s%v", {{.primaryKeyLeft}}, primary)
}
@@ -76,8 +77,9 @@ func (m *default{{.upperStartCamelObject}}Model) queryPrimary(ctx context.Contex
}
`
// FindOneMethod defines find row method.
var FindOneMethod = `FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error)`
// FindOneMethod defines find row method.
FindOneMethod = `FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error)`
// FindOneByFieldMethod defines find row by field method.
var FindOneByFieldMethod = `FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) `
// FindOneByFieldMethod defines find row by field method.
FindOneByFieldMethod = `FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) `
)

View File

@@ -1,6 +1,6 @@
package template
var (
const (
// Imports defines a import template for model in cache case
Imports = `import (
"context"

View File

@@ -1,19 +1,19 @@
package template
// Insert defines a template for insert code in model
var Insert = `
const (
// Insert defines a template for insert code in model
Insert = `
func (m *default{{.upperStartCamelObject}}Model) Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error) {
{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
{{if .withCache}}{{.keys}}
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
return conn.ExecCtx(ctx, query, {{.expressionValues}})
}, {{.keyValues}}){{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
ret,err:=m.ExecNoCacheCtx(ctx, query, {{.expressionValues}})
{{end}}{{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
ret,err:=m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}}
return ret,err
}
`
// InsertMethod defines an interface method template for insert code in model
var InsertMethod = `Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error)`
// InsertMethod defines an interface method template for insert code in model
InsertMethod = `Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error)`
)

Some files were not shown because too many files have changed in this diff Show More