Compare commits

...

81 Commits

Author SHA1 Message Date
cong
22fad4bb9c feat(trace): add trace test helpers (#3108) 2023-04-08 14:52:25 +00:00
lchjczw
189e9bd9da Fix the problem of package name for generated kt code. (#3082)
Co-authored-by: 李春华 <lichunhua@threesoft.cn>
2023-04-08 14:46:34 +00:00
Kevin Wan
98c9b5928a refactor: simplify zrpc stat config (#3107) 2023-04-08 14:45:05 +00:00
Kevin Wan
e13fd62d38 chore: coding style (#3106)
Co-authored-by: cong <zhangcong1992@gmail.com>
2023-04-08 21:37:53 +08:00
李登富
ffacae89eb fix:multipule flag client go package name (#3104)
Co-authored-by: lidengfu <lidengfu@excean.com>
2023-04-08 21:36:45 +08:00
cong
49135fe25e refactor(zrpc): prefer static config for zrpc server statinterceptor (#3105) 2023-04-08 13:03:32 +00:00
MarkJoyMa
2e6402f4b5 fix: fillDefault, handling of nested structs (#3072) 2023-04-08 11:43:13 +00:00
Kevin Wan
07f03ebd0c fix: should not conflict on lower members (#3095) 2023-04-08 14:47:57 +08:00
dependabot[bot]
92f2676afc chore(deps): bump golang.org/x/text from 0.8.0 to 0.9.0 in /tools/goctl (#3101) 2023-04-08 13:28:01 +08:00
dependabot[bot]
1807305e6d chore(deps): bump golang.org/x/net from 0.8.0 to 0.9.0 (#3100) 2023-04-08 13:27:14 +08:00
dependabot[bot]
38a97d4531 chore(deps): bump github.com/spf13/cobra from 1.6.1 to 1.7.0 in /tools/goctl (#3098)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-07 20:41:03 +08:00
dependabot[bot]
b9f98ecc4a chore(deps): bump go.mongodb.org/mongo-driver from 1.11.3 to 1.11.4 (#3097)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-07 20:22:51 +08:00
dependabot[bot]
1dc222f4b2 chore(deps): bump golang.org/x/sys from 0.6.0 to 0.7.0 (#3096)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-05 22:33:40 +08:00
cong
a79b8de24d feat(bloom): bloom support Ctx API (#3089) 2023-04-03 14:37:04 +00:00
cong
5da8a93c75 feat(redis): add ScriptRun API and migrate EvalCtx to ScriptRun for limit, lock and bloom (#3087) 2023-04-02 11:28:25 +08:00
cong
b49fc81618 refactor(redistest): simplify redistest.CreateRedis API (#3086) 2023-04-01 05:02:21 +00:00
cong
6a692453dc chore(ci): fail lint ci when gofmt or tidy check failed (#3085) 2023-04-01 03:41:17 +00:00
Kevin Wan
8d0cceb80c chore: add more tests (#3084) 2023-03-31 22:33:43 +08:00
heyehang
e06abf4f6f fixbug:superfluous response.WriteHeader (#3083) 2023-03-31 13:05:29 +00:00
Kevin Wan
ee555a85da chore: coding style (#3080) 2023-03-31 02:13:48 +00:00
anqiansong
1904af2323 feat(goctl): Support gateway sample generation (#3049) 2023-03-29 09:06:23 +00:00
cong
95b85336d6 refactor(redis): add NonBlock config, disable redis ping by default (#3073) 2023-03-29 02:28:12 +00:00
guoguangwu
ca4ce7bce8 fix : misspelled word (#3075) 2023-03-29 02:25:26 +00:00
Kevin Wan
9065eb90d9 chore: coding style (#3074) 2023-03-28 16:34:22 +00:00
anqiansong
50bc361430 feat(goctl): Add api parser (#2585) 2023-03-28 15:45:26 +00:00
Kevin Wan
455a6c8f97 Update readme-cn.md, add company users. 2023-03-27 12:22:32 +08:00
Kevin Wan
04434646eb chore: refactor zrpc setup (#3064) 2023-03-25 20:40:21 +08:00
dependabot[bot]
992a56e90b chore(deps): bump google.golang.org/grpc from 1.53.0 to 1.54.0 in /tools/goctl (#3061)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-24 16:14:44 +08:00
dependabot[bot]
ed4d5e5813 chore(deps): bump google.golang.org/grpc from 1.53.0 to 1.54.0 (#3060)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-24 11:44:26 +08:00
dependabot[bot]
fe85e7cb42 chore(deps): bump k8s.io/client-go from 0.26.2 to 0.26.3 (#3052)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-24 10:16:45 +08:00
Kevin Wan
9c6b516bb8 fix: #3058 (#3059) 2023-03-23 23:45:57 +08:00
dependabot[bot]
2e9063a9a1 chore(deps): bump go.mongodb.org/mongo-driver from 1.11.2 to 1.11.3 (#3054)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-23 22:46:50 +08:00
dependabot[bot]
c3648be533 chore(deps): bump go.uber.org/automaxprocs from 1.5.1 to 1.5.2 (#3051)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-23 22:23:47 +08:00
Kevin Wan
0ab06f62ca chore: add more tests (#3045) 2023-03-19 23:56:36 +08:00
Kevin Wan
6170d7b790 feat: rest validation on http requests (#3041) 2023-03-19 12:04:18 +00:00
cong
18d163c4f7 fix(executors): periodicalexecutor should handle crash correctly (#3043) 2023-03-18 15:04:15 +00:00
Snake
a561048d59 Fix bug: replace int and float with num type in dart (#3042)
Co-authored-by: zhoumingji <zhoumingji@cmsr.chinamobile.com>
2023-03-18 14:49:22 +00:00
dependabot[bot]
7a647ca40c chore(deps): bump google.golang.org/protobuf from 1.29.1 to 1.30.0 in /tools/goctl (#3037)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-17 20:40:36 +08:00
dependabot[bot]
3f6f14f976 chore(deps): bump google.golang.org/protobuf from 1.29.1 to 1.30.0 (#3036)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-17 20:25:43 +08:00
dependabot[bot]
a78d57bebd chore(deps): bump google.golang.org/protobuf from 1.28.2-0.20220831092852-f930b1dc76e8 to 1.29.1 in /tools/goctl (#3029)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-16 00:20:18 +08:00
dependabot[bot]
74452eb7b5 chore(deps): bump golang.org/x/text from 0.7.0 to 0.8.0 in /tools/goctl (#3028)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-15 23:56:58 +08:00
dependabot[bot]
a9e364a01a chore(deps): bump golang.org/x/net from 0.7.0 to 0.8.0 (#3027)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-15 23:32:23 +08:00
chen quan
29c2e20b41 chore(action): upgrade go 1.18 version in release (#3032) 2023-03-15 12:53:04 +00:00
dependabot[bot]
42c146bcbd chore(deps): bump google.golang.org/protobuf from 1.29.0 to 1.29.1 (#3026)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-15 11:30:19 +08:00
dependabot[bot]
b61e364458 chore(deps): bump github.com/zeromicro/go-zero from 1.4.4 to 1.5.0 in /tools/goctl (#3023) 2023-03-14 08:48:42 +08:00
dependabot[bot]
18a4dcb79f chore(deps): bump github.com/fatih/color from 1.14.1 to 1.15.0 (#3022) 2023-03-14 08:47:44 +08:00
Kevin Wan
60a13f1e53 chore: add more tests (#3018) 2023-03-12 20:42:50 +08:00
sniperwzq
3e093bf34e defines the method to customize http server (#2171) 2023-03-11 23:15:00 +08:00
Kevin Wan
211b9498ef chore: add more tests (#3016) 2023-03-11 22:22:39 +08:00
Kevin Wan
cca45be3c5 chore: refactor orm code (#3015) 2023-03-11 18:03:20 +08:00
YK.xiong
e735915d89 fix QueryRowsPartial getTaggedFieldValueMap func (#2884)
Co-authored-by: yongkun.xiong <weilone@vip.qq.com>
2023-03-11 07:28:09 +00:00
Kevin Wan
f77e2c9cfa chore: add more tests (#3014) 2023-03-11 14:57:56 +08:00
Shyunn
544aa7c432 Added zrpc server custom serverID for custom registration Key when the service is registered on ETCD. (#3008) 2023-03-11 14:34:28 +08:00
Kevin Wan
4cef2b412c fix: avoid unmarshal panic with incorrect map keys #3002 (#3013) 2023-03-11 07:53:57 +08:00
Kevin Wan
123c61ad12 chore: update readme (#3011) 2023-03-10 22:18:48 +08:00
Kevin Wan
fbf129d535 chore: add more tests (#3010) 2023-03-10 21:56:19 +08:00
Kevin Wan
c8a17a97be chore: add more tests (#3009) 2023-03-10 20:48:10 +08:00
Kevin Wan
3a493cd6a6 chore: add more tests (#3006) 2023-03-10 17:36:39 +08:00
Kevin Wan
7a0c04bc21 feat: unique redis addrs and trim spaces (#3004) 2023-03-10 16:09:07 +08:00
iyyzh
3c9fe0b381 init postgresql err (#3003) 2023-03-10 05:58:07 +00:00
文豆芽
f8b2dc8c9f reids cluster bug (#2986)
Co-authored-by: shaocongcong <shao.congcong@yalla.live>
2023-03-09 21:37:34 +08:00
taobig
37cb00d789 Export cache.Option param to NewXXXModel() (#2995) 2023-03-09 13:34:58 +00:00
dependabot[bot]
e3e7bc736b chore(deps): bump google.golang.org/protobuf from 1.28.2-0.20230222093303-bc1253ad3743 to 1.29.0 (#2993)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-09 19:56:14 +08:00
dependabot[bot]
fafbee24b8 chore(deps): bump github.com/golang/protobuf from 1.5.2 to 1.5.3 (#2992)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-09 16:24:37 +08:00
dependabot[bot]
8ec29d29ce chore(deps): bump golang.org/x/sys from 0.5.0 to 0.6.0 (#2983)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-09 15:23:02 +08:00
fabio
cb7f3e8a17 feat(redis):add LpopCount,RpopCount (#2990) 2023-03-09 05:45:45 +00:00
dependabot[bot]
03391b48ca chore(deps): bump github.com/alicebob/miniredis/v2 from 2.30.0 to 2.30.1 (#2991)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-09 13:34:48 +08:00
dependabot[bot]
d0dedb0624 chore(deps): bump github.com/jhump/protoreflect from 1.15.0 to 1.15.1 (#2982)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-08 23:55:43 +08:00
Snake
e136deb3a7 Update goclt dart gen: Add scheme config and content-type header (#2987)
Co-authored-by: zhoumingji <zhoumingji@cmsr.chinamobile.com>
2023-03-08 15:40:55 +00:00
anqiansong
a2592a17e9 format code 2023-03-08 15:32:08 +00:00
anqiansong
05abf4a2ff fix typo 2023-03-08 15:32:08 +00:00
anqiansong
d40000d4b9 fix typo 2023-03-08 15:32:08 +00:00
anqiansong
4620924105 Fix typo 2023-03-08 15:32:08 +00:00
Kevin Wan
a05fe7bf0a chore: remove optional in redis config (#2979) 2023-03-07 14:58:04 +08:00
kevin
dd347e96b0 chore: add comments 2023-03-07 01:27:41 +00:00
kevin
a972f400c6 fix: test failure 2023-03-07 01:27:41 +00:00
kevin.wan
fb7664a764 fix: config map with json tag 2023-03-07 01:27:41 +00:00
Kevin Wan
7d5d7d9085 chore: clear errors on conf conflict keys (#2972)
Co-authored-by: kevin.wan <kevin.wan@yijinin.com>
2023-03-06 12:45:17 +08:00
Kevin Wan
9911c11e9c Update FUNDING.yml 2023-03-05 22:47:17 +08:00
Kevin Wan
0d5a68869d fix: gateway conf doesn't work (#2968) 2023-03-05 22:19:58 +08:00
Kevin Wan
d9d79e930d Merge pull request from GHSA-fgxv-gw55-r5fq
* fix: Authorization Bypass Through User-Controlled Key

* chore: add not safe domain test
2023-03-04 23:34:11 +08:00
212 changed files with 15837 additions and 2058 deletions

2
.github/FUNDING.yml vendored
View File

@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg
ethereum: 0x5052b7f6B937B02563996D23feb69b38D06Ca150 | kevwan
ethereum: # 0x5052b7f6B937B02563996D23feb69b38D06Ca150 | kevwan

View File

@@ -29,8 +29,17 @@ jobs:
- name: Lint
run: |
go vet -stdmethods=false $(go list ./...)
go install mvdan.cc/gofumpt@latest
test -z "$(gofumpt -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
if ! test -z "$(gofmt -l .)"; then
echo "Please run 'gofmt -l -w .'"
exit 1
fi
go mod tidy
if ! test -z "$(git status --porcelain)"; then
echo "Please run 'go mod tidy'"
exit 1
fi
- name: Test
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...

View File

@@ -22,7 +22,7 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "https://dl.google.com/go/go1.17.5.linux-amd64.tar.gz"
goversion: "https://dl.google.com/go/go1.18.10.linux-amd64.tar.gz"
project_path: "tools/goctl"
binary_name: "goctl"
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md

View File

@@ -1,6 +1,7 @@
package bloom
import (
"context"
"errors"
"strconv"
@@ -8,28 +9,29 @@ import (
"github.com/zeromicro/go-zero/core/stores/redis"
)
const (
// for detailed error rate table, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
// maps as k in the error rate table
maps = 14
setScript = `
// for detailed error rate table, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
// maps as k in the error rate table
const maps = 14
var (
// ErrTooLargeOffset indicates the offset is too large in bitset.
ErrTooLargeOffset = errors.New("too large offset")
setScript = redis.NewScript(`
for _, offset in ipairs(ARGV) do
redis.call("setbit", KEYS[1], offset, 1)
end
`
testScript = `
`)
testScript = redis.NewScript(`
for _, offset in ipairs(ARGV) do
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
return false
end
end
return true
`
`)
)
// ErrTooLargeOffset indicates the offset is too large in bitset.
var ErrTooLargeOffset = errors.New("too large offset")
type (
// A Filter is a bloom filter.
Filter struct {
@@ -38,8 +40,8 @@ type (
}
bitSetProvider interface {
check([]uint) (bool, error)
set([]uint) error
check(ctx context.Context, offsets []uint) (bool, error)
set(ctx context.Context, offsets []uint) error
}
)
@@ -58,14 +60,24 @@ func New(store *redis.Redis, key string, bits uint) *Filter {
// Add adds data into f.
func (f *Filter) Add(data []byte) error {
return f.AddCtx(context.Background(), data)
}
// AddCtx adds data into f with context.
func (f *Filter) AddCtx(ctx context.Context, data []byte) error {
locations := f.getLocations(data)
return f.bitSet.set(locations)
return f.bitSet.set(ctx, locations)
}
// Exists checks if data is in f.
func (f *Filter) Exists(data []byte) (bool, error) {
return f.ExistsCtx(context.Background(), data)
}
// ExistsCtx checks if data is in f with context.
func (f *Filter) ExistsCtx(ctx context.Context, data []byte) (bool, error) {
locations := f.getLocations(data)
isSet, err := f.bitSet.check(locations)
isSet, err := f.bitSet.check(ctx, locations)
if err != nil {
return false, err
}
@@ -111,13 +123,13 @@ func (r *redisBitSet) buildOffsetArgs(offsets []uint) ([]string, error) {
return args, nil
}
func (r *redisBitSet) check(offsets []uint) (bool, error) {
func (r *redisBitSet) check(ctx context.Context, offsets []uint) (bool, error) {
args, err := r.buildOffsetArgs(offsets)
if err != nil {
return false, err
}
resp, err := r.store.Eval(testScript, []string{r.key}, args)
resp, err := r.store.ScriptRunCtx(ctx, testScript, []string{r.key}, args)
if err == redis.Nil {
return false, nil
} else if err != nil {
@@ -132,22 +144,24 @@ func (r *redisBitSet) check(offsets []uint) (bool, error) {
return exists == 1, nil
}
// del only use for testing.
func (r *redisBitSet) del() error {
_, err := r.store.Del(r.key)
return err
}
// expire only use for testing.
func (r *redisBitSet) expire(seconds int) error {
return r.store.Expire(r.key, seconds)
}
func (r *redisBitSet) set(offsets []uint) error {
func (r *redisBitSet) set(ctx context.Context, offsets []uint) error {
args, err := r.buildOffsetArgs(offsets)
if err != nil {
return err
}
_, err = r.store.Eval(setScript, []string{r.key}, args)
_, err = r.store.ScriptRunCtx(ctx, setScript, []string{r.key}, args)
if err == redis.Nil {
return nil
}

View File

@@ -1,30 +1,31 @@
package bloom
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/redis/redistest"
)
func TestRedisBitSet_New_Set_Test(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
ctx := context.Background()
bitSet := newRedisBitSet(store, "test_key", 1024)
isSetBefore, err := bitSet.check([]uint{0})
isSetBefore, err := bitSet.check(ctx, []uint{0})
if err != nil {
t.Fatal(err)
}
if isSetBefore {
t.Fatal("Bit should not be set")
}
err = bitSet.set([]uint{512})
err = bitSet.set(ctx, []uint{512})
if err != nil {
t.Fatal(err)
}
isSetAfter, err := bitSet.check([]uint{512})
isSetAfter, err := bitSet.check(ctx, []uint{512})
if err != nil {
t.Fatal(err)
}
@@ -42,9 +43,7 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
}
func TestRedisBitSet_Add(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
filter := New(store, "test_key", 64)
assert.Nil(t, filter.Add([]byte("hello")))
@@ -53,3 +52,51 @@ func TestRedisBitSet_Add(t *testing.T) {
assert.Nil(t, err)
assert.True(t, ok)
}
func TestFilter_Exists(t *testing.T) {
store, clean := redistest.CreateRedisWithClean(t)
rbs := New(store, "test", 64)
_, err := rbs.Exists([]byte{0, 1, 2})
assert.NoError(t, err)
clean()
rbs = New(store, "test", 64)
_, err = rbs.Exists([]byte{0, 1, 2})
assert.Error(t, err)
}
func TestRedisBitSet_check(t *testing.T) {
store, clean := redistest.CreateRedisWithClean(t)
ctx := context.Background()
rbs := newRedisBitSet(store, "test", 0)
assert.Error(t, rbs.set(ctx, []uint{0, 1, 2}))
_, err := rbs.check(ctx, []uint{0, 1, 2})
assert.Error(t, err)
rbs = newRedisBitSet(store, "test", 64)
_, err = rbs.check(ctx, []uint{0, 1, 2})
assert.NoError(t, err)
clean()
rbs = newRedisBitSet(store, "test", 64)
_, err = rbs.check(ctx, []uint{0, 1, 2})
assert.Error(t, err)
}
func TestRedisBitSet_set(t *testing.T) {
logx.Disable()
store, clean := redistest.CreateRedisWithClean(t)
ctx := context.Background()
rbs := newRedisBitSet(store, "test", 0)
assert.Error(t, rbs.set(ctx, []uint{0, 1, 2}))
rbs = newRedisBitSet(store, "test", 64)
assert.NoError(t, rbs.set(ctx, []uint{0, 1, 2}))
clean()
rbs = newRedisBitSet(store, "test", 64)
assert.Error(t, rbs.set(ctx, []uint{0, 1, 2}))
}

View File

@@ -2,6 +2,8 @@ package codec
import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"testing"
@@ -21,3 +23,45 @@ func TestGzip(t *testing.T) {
assert.True(t, len(bs) < buf.Len())
assert.Equal(t, buf.Bytes(), actual)
}
func TestGunzip(t *testing.T) {
tests := []struct {
name string
input []byte
expected []byte
expectedErr error
}{
{
name: "valid input",
input: func() []byte {
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
gz.Write([]byte("hello"))
gz.Close()
return buf.Bytes()
}(),
expected: []byte("hello"),
expectedErr: nil,
},
{
name: "invalid input",
input: []byte("invalid input"),
expected: nil,
expectedErr: gzip.ErrHeader,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result, err := Gunzip(test.input)
if !bytes.Equal(result, test.expected) {
t.Errorf("unexpected result: %v", result)
}
if !errors.Is(err, test.expectedErr) {
t.Errorf("unexpected error: %v", err)
}
})
}
}

View File

@@ -13,7 +13,10 @@ import (
"github.com/zeromicro/go-zero/internal/encoding"
)
const jsonTagKey = "json"
const (
jsonTagKey = "json"
jsonTagSep = ','
)
var (
fillDefaultUnmarshaler = mapping.NewUnmarshaler(jsonTagKey, mapping.WithDefault())
@@ -76,7 +79,7 @@ func LoadFromJsonBytes(content []byte, v any) error {
}
var m map[string]any
if err := jsonx.Unmarshal(content, &m); err != nil {
if err = jsonx.Unmarshal(content, &m); err != nil {
return err
}
@@ -127,7 +130,7 @@ func MustLoad(path string, v any, opts ...Option) {
func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo) error {
if prev, ok := info.children[key]; ok {
if child.mapField != nil {
return newDupKeyError(key)
return newConflictKeyError(key)
}
if err := mergeFields(prev, key, child.children); err != nil {
@@ -160,7 +163,7 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
}
if _, ok := info.children[lowerCaseName]; ok {
return newDupKeyError(lowerCaseName)
return newConflictKeyError(lowerCaseName)
}
info.children[lowerCaseName] = &fieldInfo{
@@ -169,7 +172,7 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
}
default:
if _, ok := info.children[lowerCaseName]; ok {
return newDupKeyError(lowerCaseName)
return newConflictKeyError(lowerCaseName)
}
info.children[lowerCaseName] = &fieldInfo{
@@ -239,7 +242,11 @@ func buildStructFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
for i := 0; i < tp.NumField(); i++ {
field := tp.Field(i)
name := field.Name
if !field.IsExported() {
continue
}
name := getTagName(field)
lowerCaseName := toLowerCase(name)
ft := mapping.Deref(field.Type)
// flatten anonymous fields
@@ -255,15 +262,32 @@ func buildStructFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
return info, nil
}
// getTagName get the tag name of the given field, if no tag name, use file.Name.
// field.Name is returned on tags like `json:""` and `json:",optional"`.
func getTagName(field reflect.StructField) string {
if tag, ok := field.Tag.Lookup(jsonTagKey); ok {
if pos := strings.IndexByte(tag, jsonTagSep); pos >= 0 {
tag = tag[:pos]
}
tag = strings.TrimSpace(tag)
if len(tag) > 0 {
return tag
}
}
return field.Name
}
func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo) error {
if len(prev.children) == 0 || len(children) == 0 {
return newDupKeyError(key)
return newConflictKeyError(key)
}
// merge fields
for k, v := range children {
if _, ok := prev.children[k]; ok {
return newDupKeyError(k)
return newConflictKeyError(k)
}
prev.children[k] = v
@@ -314,14 +338,14 @@ func toLowerCaseKeyMap(m map[string]any, info *fieldInfo) map[string]any {
return res
}
type dupKeyError struct {
type conflictKeyError struct {
key string
}
func newDupKeyError(key string) dupKeyError {
return dupKeyError{key: key}
func newConflictKeyError(key string) conflictKeyError {
return conflictKeyError{key: key}
}
func (e dupKeyError) Error() string {
return fmt.Sprintf("duplicated key %s", e.key)
func (e conflictKeyError) Error() string {
return fmt.Sprintf("conflict key %s, pay attention to anonymous fields", e.key)
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/zeromicro/go-zero/core/hash"
)
var dupErr dupKeyError
var dupErr conflictKeyError
func TestLoadConfig_notExists(t *testing.T) {
assert.NotNil(t, Load("not_a_file", nil))
@@ -123,6 +123,24 @@ d = "abcd"
}
}
func TestConfigWithLower(t *testing.T) {
text := `a = "foo"
b = 1
`
tmpfile, err := createTempFile(".toml", text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
b int
}
if assert.NoError(t, Load(tmpfile, &val)) {
assert.Equal(t, "foo", val.A)
assert.Equal(t, 0, val.b)
}
}
func TestConfigJsonCanonical(t *testing.T) {
text := []byte(`{"a": "foo", "B": "bar"}`)
@@ -672,7 +690,7 @@ func Test_FieldOverwrite(t *testing.T) {
input := []byte(`{"Name": "hello"}`)
err := LoadFromJsonBytes(input, val)
assert.ErrorAs(t, err, &dupErr)
assert.Equal(t, newDupKeyError("name").Error(), err.Error())
assert.Equal(t, newConflictKeyError("name").Error(), err.Error())
}
validate(&St1{})
@@ -715,7 +733,7 @@ func Test_FieldOverwrite(t *testing.T) {
input := []byte(`{"Name": "hello"}`)
err := LoadFromJsonBytes(input, val)
assert.ErrorAs(t, err, &dupErr)
assert.Equal(t, newDupKeyError("name").Error(), err.Error())
assert.Equal(t, newConflictKeyError("name").Error(), err.Error())
}
validate(&St0{})
@@ -1022,22 +1040,22 @@ func TestLoadNamedFieldOverwritten(t *testing.T) {
})
}
func createTempFile(ext, text string) (string, error) {
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
if err != nil {
return "", err
}
func TestLoadLowerMemberShouldNotConflict(t *testing.T) {
type (
Redis struct {
db uint
}
if err := os.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
return "", err
}
Config struct {
db uint
Redis
}
)
filename := tmpfile.Name()
if err = tmpfile.Close(); err != nil {
return "", err
}
return filename, nil
var c Config
assert.NoError(t, LoadFromJsonBytes([]byte(`{}`), &c))
assert.Zero(t, c.db)
assert.Zero(t, c.Redis.db)
}
func TestFillDefaultUnmarshal(t *testing.T) {
@@ -1079,7 +1097,7 @@ func TestFillDefaultUnmarshal(t *testing.T) {
assert.Equal(t, st.C, "c")
})
t.Run("has vaue", func(t *testing.T) {
t.Run("has value", func(t *testing.T) {
type St struct {
A string `json:",default=a"`
B string
@@ -1091,3 +1109,107 @@ func TestFillDefaultUnmarshal(t *testing.T) {
assert.Error(t, err)
})
}
func TestConfigWithJsonTag(t *testing.T) {
t.Run("map with value", func(t *testing.T) {
var input = []byte(`[Value]
[Value.first]
Email = "foo"
[Value.second]
Email = "bar"`)
type Value struct {
Email string
}
type Config struct {
ValueMap map[string]Value `json:"Value"`
}
var c Config
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
assert.Len(t, c.ValueMap, 2)
}
})
t.Run("map with ptr value", func(t *testing.T) {
var input = []byte(`[Value]
[Value.first]
Email = "foo"
[Value.second]
Email = "bar"`)
type Value struct {
Email string
}
type Config struct {
ValueMap map[string]*Value `json:"Value"`
}
var c Config
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
assert.Len(t, c.ValueMap, 2)
}
})
t.Run("map with optional", func(t *testing.T) {
var input = []byte(`[Value]
[Value.first]
Email = "foo"
[Value.second]
Email = "bar"`)
type Value struct {
Email string
}
type Config struct {
Value map[string]Value `json:",optional"`
}
var c Config
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
assert.Len(t, c.Value, 2)
}
})
t.Run("map with empty tag", func(t *testing.T) {
var input = []byte(`[Value]
[Value.first]
Email = "foo"
[Value.second]
Email = "bar"`)
type Value struct {
Email string
}
type Config struct {
Value map[string]Value `json:" "`
}
var c Config
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
assert.Len(t, c.Value, 2)
}
})
}
func createTempFile(ext, text string) (string, error) {
tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
if err != nil {
return "", err
}
if err := os.WriteFile(tmpFile.Name(), []byte(text), os.ModeTemporary); err != nil {
return "", err
}
filename := tmpFile.Name()
if err = tmpFile.Close(); err != nil {
return "", err
}
return filename, nil
}

View File

@@ -13,6 +13,7 @@ var (
type EtcdConf struct {
Hosts []string
Key string
ID int64 `json:",optional"`
User string `json:",optional"`
Pass string `json:",optional"`
CertFile string `json:",optional"`
@@ -26,6 +27,11 @@ func (c EtcdConf) HasAccount() bool {
return len(c.User) > 0 && len(c.Pass) > 0
}
// HasID returns if ID provided.
func (c EtcdConf) HasID() bool {
return c.ID > 0
}
// HasTLS returns if TLS CertFile/CertKeyFile/CACertFile are provided.
func (c EtcdConf) HasTLS() bool {
return len(c.CertFile) > 0 && len(c.CertKeyFile) > 0 && len(c.CACertFile) > 0

View File

@@ -80,3 +80,90 @@ func TestEtcdConf_HasAccount(t *testing.T) {
assert.Equal(t, test.hasAccount, test.EtcdConf.HasAccount())
}
}
func TestEtcdConf_HasID(t *testing.T) {
tests := []struct {
EtcdConf
hasServerID bool
}{
{
EtcdConf: EtcdConf{
Hosts: []string{"any"},
ID: -1,
},
hasServerID: false,
},
{
EtcdConf: EtcdConf{
Hosts: []string{"any"},
ID: 0,
},
hasServerID: false,
},
{
EtcdConf: EtcdConf{
Hosts: []string{"any"},
ID: 10000,
},
hasServerID: true,
},
}
for _, test := range tests {
assert.Equal(t, test.hasServerID, test.EtcdConf.HasID())
}
}
func TestEtcdConf_HasTLS(t *testing.T) {
tests := []struct {
name string
conf EtcdConf
want bool
}{
{
name: "empty config",
conf: EtcdConf{},
want: false,
},
{
name: "missing CertFile",
conf: EtcdConf{
CertKeyFile: "key",
CACertFile: "ca",
},
want: false,
},
{
name: "missing CertKeyFile",
conf: EtcdConf{
CertFile: "cert",
CACertFile: "ca",
},
want: false,
},
{
name: "missing CACertFile",
conf: EtcdConf{
CertFile: "cert",
CertKeyFile: "key",
},
want: false,
},
{
name: "valid config",
conf: EtcdConf{
CertFile: "cert",
CertKeyFile: "key",
CACertFile: "ca",
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.conf.HasTLS()
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -1,12 +1,85 @@
package internal
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stringx"
)
const (
certContent = `-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUEg9GVO2oaPn+YSmiqmFIuAo10WIwDQYJKoZIhvcNAQEM
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMjNaGA8yMTIz
MDIxNTEzMjEyM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALplXlWsIf0O/IgnIplmiZHKGnxyfyufyE2FBRNk
OofRqbKuPH8GNqbkvZm7N29fwTDAQ+mViAggCkDht4hOzoWJMA7KYJt8JnTSWL48
M1lcrpc9DL2gszC/JF/FGvyANbBtLklkZPFBGdHUX14pjrT937wqPtm+SqUHSvRT
B7bmwmm2drRcmhpVm98LSlV7uQ2EgnJgsLjBPITKUejLmVLHfgX0RwQ2xIpX9pS4
FCe1BTacwl2gGp7Mje7y4Mfv3o0ArJW6Tuwbjx59ZXwb1KIP71b7bT04AVS8ZeYO
UMLKKuB5UR9x9Rn6cLXOTWBpcMVyzDgrAFLZjnE9LPUolZMCAwEAAaNRME8wHwYD
VR0jBBgwFoAUeW8w8pmhncbRgTsl48k4/7wnfx8wCQYDVR0TBAIwADALBgNVHQ8E
BAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBDAUAA4IBAQAI
y9xaoS88CLPBsX6mxfcTAFVfGNTRW9VN9Ng1cCnUR+YGoXGM/l+qP4f7p8ocdGwK
iYZErVTzXYIn+D27//wpY3klJk3gAnEUBT3QRkStBw7XnpbeZ2oPBK+cmDnCnZPS
BIF1wxPX7vIgaxs5Zsdqwk3qvZ4Djr2wP7LabNWTLSBKgQoUY45Liw6pffLwcGF9
UKlu54bvGze2SufISCR3ib+I+FLvqpvJhXToZWYb/pfI/HccuCL1oot1x8vx6DQy
U+TYxlZsKS5mdNxAX3dqEkEMsgEi+g/tzDPXJImfeCGGBhIOXLm8SRypiuGdEbc9
xkWYxRPegajuEZGvCqVs
-----END CERTIFICATE-----`
keyContent = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAumVeVawh/Q78iCcimWaJkcoafHJ/K5/ITYUFE2Q6h9Gpsq48
fwY2puS9mbs3b1/BMMBD6ZWICCAKQOG3iE7OhYkwDspgm3wmdNJYvjwzWVyulz0M
vaCzML8kX8Ua/IA1sG0uSWRk8UEZ0dRfXimOtP3fvCo+2b5KpQdK9FMHtubCabZ2
tFyaGlWb3wtKVXu5DYSCcmCwuME8hMpR6MuZUsd+BfRHBDbEilf2lLgUJ7UFNpzC
XaAansyN7vLgx+/ejQCslbpO7BuPHn1lfBvUog/vVvttPTgBVLxl5g5Qwsoq4HlR
H3H1Gfpwtc5NYGlwxXLMOCsAUtmOcT0s9SiVkwIDAQABAoIBAD5meTJNMgO55Kjg
ESExxpRcCIno+tHr5+6rvYtEXqPheOIsmmwb9Gfi4+Z3WpOaht5/Pz0Ppj6yGzyl
U//6AgGKb+BDuBvVcDpjwPnOxZIBCSHwejdxeQu0scSuA97MPS0XIAvJ5FEv7ijk
5Bht6SyGYURpECltHygoTNuGgGqmO+McCJRLE9L09lTBI6UQ/JQwWJqSr7wx6iPU
M1Ze/srIV+7cyEPu6i0DGjS1gSQKkX68Lqn1w6oE290O+OZvleO0gZ02fLDWCZke
aeD9+EU/Pw+rqm3H6o0szOFIpzhRp41FUdW9sybB3Yp3u7c/574E+04Z/e30LMKs
TCtE1QECgYEA3K7KIpw0NH2HXL5C3RHcLmr204xeBfS70riBQQuVUgYdmxak2ima
80RInskY8hRhSGTg0l+VYIH8cmjcUyqMSOELS5XfRH99r4QPiK8AguXg80T4VumY
W3Pf+zEC2ssgP/gYthV0g0Xj5m2QxktOF9tRw5nkg739ZR4dI9lm/iECgYEA2Dnf
uwEDGqHiQRF6/fh5BG/nGVMvrefkqx6WvTJQ3k/M/9WhxB+lr/8yH46TuS8N2b29
FoTf3Mr9T7pr/PWkOPzoY3P56nYbKU8xSwCim9xMzhBMzj8/N9ukJvXy27/VOz56
eQaKqnvdXNGtPJrIMDGHps2KKWlKLyAlapzjVTMCgYAA/W++tACv85g13EykfT4F
n0k4LbsGP9DP4zABQLIMyiY72eAncmRVjwrcW36XJ2xATOONTgx3gF3HjZzfaqNy
eD/6uNNllUTVEryXGmHgNHPL45VRnn6memCY2eFvZdXhM5W4y2PYaunY0MkDercA
+GTngbs6tBF88KOk04bYwQKBgFl68cRgsdkmnwwQYNaTKfmVGYzYaQXNzkqmWPko
xmCJo6tHzC7ubdG8iRCYHzfmahPuuj6EdGPZuSRyYFgJi5Ftz/nAN+84OxtIQ3zn
YWOgskQgaLh9YfsKsQ7Sf1NDOsnOnD5TX7UXl07fEpLe9vNCvAFiU8e5Y9LGudU5
4bYTAoGBAMdX3a3bXp4cZvXNBJ/QLVyxC6fP1Q4haCR1Od3m+T00Jth2IX2dk/fl
p6xiJT1av5JtYabv1dFKaXOS5s1kLGGuCCSKpkvFZm826aQ2AFm0XGqEQDLeei5b
A52Kpy/YJ+RkG4BTFtAooFq6DmA0cnoP6oPvG2h6XtDJwDTPInJb
-----END RSA PRIVATE KEY-----`
caContent = `-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIUBJvFoCowKich7MMfseJ+DYzzirowDQYJKoZIhvcNAQEM
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMDNaGA8yMTIz
MDIxNTEzMjEwM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAO4to2YMYj0bxgr2FCiweSTSFuPx33zSw2x/s9Wf
OR41bm2DFsyYT5f3sOIKlXZEdLmOKty2e3ho3yC0EyNpVHdykkkHT3aDI17quZax
kYi/URqqtl1Z08A22txolc04hAZisg2BypGi3vql81UW1t3zyloGnJoIAeXR9uca
ljP6Bk3bwsxoVBLi1JtHrO0hHLQaeHmKhAyrys06X0LRdn7Px48yRZlt6FaLSa8X
YiRM0G44bVy/h6BkoQjMYGwVmCVk6zjJ9U7ZPFqdnDMNxAfR+hjDnYodqdLDMTTR
1NPVrnEnNwFx0AMLvgt/ba/45vZCEAmSZnFXFAJJcM7ai9ECAwEAAaNTMFEwHQYD
VR0OBBYEFHlvMPKZoZ3G0YE7JePJOP+8J38fMB8GA1UdIwQYMBaAFHlvMPKZoZ3G
0YE7JePJOP+8J38fMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggEB
AMX8dNulADOo9uQgBMyFb9TVra7iY0zZjzv4GY5XY7scd52n6CnfAPvYBBDnTr/O
BgNp5jaujb4+9u/2qhV3f9n+/3WOb2CmPehBgVSzlXqHeQ9lshmgwZPeem2T+8Tm
Nnc/xQnsUfCFszUDxpkr55+aLVM22j02RWqcZ4q7TAaVYL+kdFVMc8FoqG/0ro6A
BjE/Qn0Nn7ciX1VUjDt8l+k7ummPJTmzdi6i6E4AwO9dzrGNgGJ4aWL8cC6xYcIX
goVIRTFeONXSDno/oPjWHpIPt7L15heMpKBHNuzPkKx2YVqPHE5QZxWfS+Lzgx+Q
E2oTTM0rYKOZ8p6000mhvKI=
-----END CERTIFICATE-----`
)
func TestAccount(t *testing.T) {
endpoints := []string{
"192.168.0.2:2379",
@@ -32,3 +105,34 @@ func TestAccount(t *testing.T) {
assert.Equal(t, username, account.User)
assert.Equal(t, anotherPassword, account.Pass)
}
func TestTLSMethods(t *testing.T) {
certFile := createTempFile(t, []byte(certContent))
defer os.Remove(certFile)
keyFile := createTempFile(t, []byte(keyContent))
defer os.Remove(keyFile)
caFile := createTempFile(t, []byte(caContent))
defer os.Remove(caFile)
assert.NoError(t, AddTLS([]string{"foo"}, certFile, keyFile, caFile, false))
cfg, ok := GetTLS([]string{"foo"})
assert.True(t, ok)
assert.NotNil(t, cfg)
assert.Error(t, AddTLS([]string{"bar"}, "bad-file", keyFile, caFile, false))
assert.Error(t, AddTLS([]string{"bar"}, certFile, keyFile, "bad-file", false))
}
func createTempFile(t *testing.T, body []byte) string {
tmpFile, err := os.CreateTemp(os.TempDir(), "go-unit-*.tmp")
if err != nil {
t.Fatal(err)
}
tmpFile.Close()
if err = os.WriteFile(tmpFile.Name(), body, os.ModePerm); err != nil {
t.Fatal(err)
}
return tmpFile.Name()
}

View File

@@ -2,8 +2,10 @@ package internal
import (
"context"
"os"
"sync"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
@@ -14,6 +16,7 @@ import (
"go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/mvccpb"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/mock/mockserver"
)
var mockLock sync.Mutex
@@ -242,3 +245,58 @@ func TestValueOnlyContext(t *testing.T) {
ctx.Done()
assert.Nil(t, ctx.Err())
}
func TestDialClient(t *testing.T) {
svr, err := mockserver.StartMockServers(1)
assert.NoError(t, err)
svr.StartAt(0)
certFile := createTempFile(t, []byte(certContent))
defer os.Remove(certFile)
keyFile := createTempFile(t, []byte(keyContent))
defer os.Remove(keyFile)
caFile := createTempFile(t, []byte(caContent))
defer os.Remove(caFile)
endpoints := []string{svr.Servers[0].Address}
AddAccount(endpoints, "foo", "bar")
assert.NoError(t, AddTLS(endpoints, certFile, keyFile, caFile, false))
old := DialTimeout
DialTimeout = time.Millisecond
defer func() {
DialTimeout = old
}()
_, err = DialClient(endpoints)
assert.Error(t, err)
}
func TestRegistry_Monitor(t *testing.T) {
svr, err := mockserver.StartMockServers(1)
assert.NoError(t, err)
svr.StartAt(0)
endpoints := []string{svr.Servers[0].Address}
GetRegistry().lock.Lock()
GetRegistry().clusters = map[string]*cluster{
getClusterKey(endpoints): {
listeners: map[string][]UpdateListener{},
values: map[string]map[string]string{
"foo": {
"bar": "baz",
},
},
},
}
GetRegistry().lock.Unlock()
assert.Error(t, GetRegistry().Monitor(endpoints, "foo", new(mockListener)))
}
type mockListener struct {
}
func (m *mockListener) OnAdd(_ KV) {
}
func (m *mockListener) OnDelete(_ KV) {
}

View File

@@ -1,7 +1,10 @@
package discov
import (
"context"
"errors"
"net"
"os"
"sync"
"testing"
"time"
@@ -13,6 +16,83 @@ import (
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stringx"
clientv3 "go.etcd.io/etcd/client/v3"
"golang.org/x/net/http2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
)
const (
certContent = `-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUEg9GVO2oaPn+YSmiqmFIuAo10WIwDQYJKoZIhvcNAQEM
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMjNaGA8yMTIz
MDIxNTEzMjEyM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALplXlWsIf0O/IgnIplmiZHKGnxyfyufyE2FBRNk
OofRqbKuPH8GNqbkvZm7N29fwTDAQ+mViAggCkDht4hOzoWJMA7KYJt8JnTSWL48
M1lcrpc9DL2gszC/JF/FGvyANbBtLklkZPFBGdHUX14pjrT937wqPtm+SqUHSvRT
B7bmwmm2drRcmhpVm98LSlV7uQ2EgnJgsLjBPITKUejLmVLHfgX0RwQ2xIpX9pS4
FCe1BTacwl2gGp7Mje7y4Mfv3o0ArJW6Tuwbjx59ZXwb1KIP71b7bT04AVS8ZeYO
UMLKKuB5UR9x9Rn6cLXOTWBpcMVyzDgrAFLZjnE9LPUolZMCAwEAAaNRME8wHwYD
VR0jBBgwFoAUeW8w8pmhncbRgTsl48k4/7wnfx8wCQYDVR0TBAIwADALBgNVHQ8E
BAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBDAUAA4IBAQAI
y9xaoS88CLPBsX6mxfcTAFVfGNTRW9VN9Ng1cCnUR+YGoXGM/l+qP4f7p8ocdGwK
iYZErVTzXYIn+D27//wpY3klJk3gAnEUBT3QRkStBw7XnpbeZ2oPBK+cmDnCnZPS
BIF1wxPX7vIgaxs5Zsdqwk3qvZ4Djr2wP7LabNWTLSBKgQoUY45Liw6pffLwcGF9
UKlu54bvGze2SufISCR3ib+I+FLvqpvJhXToZWYb/pfI/HccuCL1oot1x8vx6DQy
U+TYxlZsKS5mdNxAX3dqEkEMsgEi+g/tzDPXJImfeCGGBhIOXLm8SRypiuGdEbc9
xkWYxRPegajuEZGvCqVs
-----END CERTIFICATE-----`
keyContent = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAumVeVawh/Q78iCcimWaJkcoafHJ/K5/ITYUFE2Q6h9Gpsq48
fwY2puS9mbs3b1/BMMBD6ZWICCAKQOG3iE7OhYkwDspgm3wmdNJYvjwzWVyulz0M
vaCzML8kX8Ua/IA1sG0uSWRk8UEZ0dRfXimOtP3fvCo+2b5KpQdK9FMHtubCabZ2
tFyaGlWb3wtKVXu5DYSCcmCwuME8hMpR6MuZUsd+BfRHBDbEilf2lLgUJ7UFNpzC
XaAansyN7vLgx+/ejQCslbpO7BuPHn1lfBvUog/vVvttPTgBVLxl5g5Qwsoq4HlR
H3H1Gfpwtc5NYGlwxXLMOCsAUtmOcT0s9SiVkwIDAQABAoIBAD5meTJNMgO55Kjg
ESExxpRcCIno+tHr5+6rvYtEXqPheOIsmmwb9Gfi4+Z3WpOaht5/Pz0Ppj6yGzyl
U//6AgGKb+BDuBvVcDpjwPnOxZIBCSHwejdxeQu0scSuA97MPS0XIAvJ5FEv7ijk
5Bht6SyGYURpECltHygoTNuGgGqmO+McCJRLE9L09lTBI6UQ/JQwWJqSr7wx6iPU
M1Ze/srIV+7cyEPu6i0DGjS1gSQKkX68Lqn1w6oE290O+OZvleO0gZ02fLDWCZke
aeD9+EU/Pw+rqm3H6o0szOFIpzhRp41FUdW9sybB3Yp3u7c/574E+04Z/e30LMKs
TCtE1QECgYEA3K7KIpw0NH2HXL5C3RHcLmr204xeBfS70riBQQuVUgYdmxak2ima
80RInskY8hRhSGTg0l+VYIH8cmjcUyqMSOELS5XfRH99r4QPiK8AguXg80T4VumY
W3Pf+zEC2ssgP/gYthV0g0Xj5m2QxktOF9tRw5nkg739ZR4dI9lm/iECgYEA2Dnf
uwEDGqHiQRF6/fh5BG/nGVMvrefkqx6WvTJQ3k/M/9WhxB+lr/8yH46TuS8N2b29
FoTf3Mr9T7pr/PWkOPzoY3P56nYbKU8xSwCim9xMzhBMzj8/N9ukJvXy27/VOz56
eQaKqnvdXNGtPJrIMDGHps2KKWlKLyAlapzjVTMCgYAA/W++tACv85g13EykfT4F
n0k4LbsGP9DP4zABQLIMyiY72eAncmRVjwrcW36XJ2xATOONTgx3gF3HjZzfaqNy
eD/6uNNllUTVEryXGmHgNHPL45VRnn6memCY2eFvZdXhM5W4y2PYaunY0MkDercA
+GTngbs6tBF88KOk04bYwQKBgFl68cRgsdkmnwwQYNaTKfmVGYzYaQXNzkqmWPko
xmCJo6tHzC7ubdG8iRCYHzfmahPuuj6EdGPZuSRyYFgJi5Ftz/nAN+84OxtIQ3zn
YWOgskQgaLh9YfsKsQ7Sf1NDOsnOnD5TX7UXl07fEpLe9vNCvAFiU8e5Y9LGudU5
4bYTAoGBAMdX3a3bXp4cZvXNBJ/QLVyxC6fP1Q4haCR1Od3m+T00Jth2IX2dk/fl
p6xiJT1av5JtYabv1dFKaXOS5s1kLGGuCCSKpkvFZm826aQ2AFm0XGqEQDLeei5b
A52Kpy/YJ+RkG4BTFtAooFq6DmA0cnoP6oPvG2h6XtDJwDTPInJb
-----END RSA PRIVATE KEY-----`
caContent = `-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIUBJvFoCowKich7MMfseJ+DYzzirowDQYJKoZIhvcNAQEM
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMDNaGA8yMTIz
MDIxNTEzMjEwM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAO4to2YMYj0bxgr2FCiweSTSFuPx33zSw2x/s9Wf
OR41bm2DFsyYT5f3sOIKlXZEdLmOKty2e3ho3yC0EyNpVHdykkkHT3aDI17quZax
kYi/URqqtl1Z08A22txolc04hAZisg2BypGi3vql81UW1t3zyloGnJoIAeXR9uca
ljP6Bk3bwsxoVBLi1JtHrO0hHLQaeHmKhAyrys06X0LRdn7Px48yRZlt6FaLSa8X
YiRM0G44bVy/h6BkoQjMYGwVmCVk6zjJ9U7ZPFqdnDMNxAfR+hjDnYodqdLDMTTR
1NPVrnEnNwFx0AMLvgt/ba/45vZCEAmSZnFXFAJJcM7ai9ECAwEAAaNTMFEwHQYD
VR0OBBYEFHlvMPKZoZ3G0YE7JePJOP+8J38fMB8GA1UdIwQYMBaAFHlvMPKZoZ3G
0YE7JePJOP+8J38fMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggEB
AMX8dNulADOo9uQgBMyFb9TVra7iY0zZjzv4GY5XY7scd52n6CnfAPvYBBDnTr/O
BgNp5jaujb4+9u/2qhV3f9n+/3WOb2CmPehBgVSzlXqHeQ9lshmgwZPeem2T+8Tm
Nnc/xQnsUfCFszUDxpkr55+aLVM22j02RWqcZ4q7TAaVYL+kdFVMc8FoqG/0ro6A
BjE/Qn0Nn7ciX1VUjDt8l+k7ummPJTmzdi6i6E4AwO9dzrGNgGJ4aWL8cC6xYcIX
goVIRTFeONXSDno/oPjWHpIPt7L15heMpKBHNuzPkKx2YVqPHE5QZxWfS+Lzgx+Q
E2oTTM0rYKOZ8p6000mhvKI=
-----END CERTIFICATE-----`
)
func init() {
@@ -37,7 +117,7 @@ func TestPublisher_register(t *testing.T) {
assert.Nil(t, err)
}
func TestPublisher_registerWithId(t *testing.T) {
func TestPublisher_registerWithOptions(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
const id = 2
@@ -49,7 +129,15 @@ func TestPublisher_registerWithId(t *testing.T) {
ID: 1,
}, nil)
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", id), "thevalue", gomock.Any())
pub := NewPublisher(nil, "thekey", "thevalue", WithId(id))
certFile := createTempFile(t, []byte(certContent))
defer os.Remove(certFile)
keyFile := createTempFile(t, []byte(keyContent))
defer os.Remove(keyFile)
caFile := createTempFile(t, []byte(caContent))
defer os.Remove(caFile)
pub := NewPublisher(nil, "thekey", "thevalue", WithId(id),
WithPubEtcdTLS(certFile, keyFile, caFile, true))
_, err := pub.register(cli)
assert.Nil(t, err)
}
@@ -169,3 +257,92 @@ func TestPublisher_Resume(t *testing.T) {
}()
<-publisher.resumeChan
}
func TestPublisher_keepAliveAsync(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
const id clientv3.LeaseID = 1
conn := createMockConn(t)
defer conn.Close()
cli := internal.NewMockEtcdClient(ctrl)
cli.EXPECT().ActiveConnection().Return(conn).AnyTimes()
cli.EXPECT().Close()
defer cli.Close()
cli.ActiveConnection()
restore := setMockClient(cli)
defer restore()
cli.EXPECT().Ctx().AnyTimes()
cli.EXPECT().KeepAlive(gomock.Any(), id)
cli.EXPECT().Grant(gomock.Any(), timeToLive).Return(&clientv3.LeaseGrantResponse{
ID: 1,
}, nil)
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", int64(id)), "thevalue", gomock.Any())
var wg sync.WaitGroup
wg.Add(1)
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ any) {
wg.Done()
})
pub := NewPublisher([]string{"the-endpoint"}, "thekey", "thevalue")
pub.lease = id
assert.Nil(t, pub.KeepAlive())
pub.Stop()
wg.Wait()
}
func createMockConn(t *testing.T) *grpc.ClientConn {
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("Error while listening. Err: %v", err)
}
defer lis.Close()
lisAddr := resolver.Address{Addr: lis.Addr().String()}
lisDone := make(chan struct{})
dialDone := make(chan struct{})
// 1st listener accepts the connection and then does nothing
go func() {
defer close(lisDone)
conn, err := lis.Accept()
if err != nil {
t.Errorf("Error while accepting. Err: %v", err)
return
}
framer := http2.NewFramer(conn, conn)
if err := framer.WriteSettings(http2.Setting{}); err != nil {
t.Errorf("Error while writing settings. Err: %v", err)
return
}
<-dialDone // Close conn only after dial returns.
}()
r := manual.NewBuilderWithScheme("whatever")
r.InitialState(resolver.State{Addresses: []resolver.Address{lisAddr}})
client, err := grpc.DialContext(context.Background(), r.Scheme()+":///test.server",
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(r))
close(dialDone)
if err != nil {
t.Fatalf("Dial failed. Err: %v", err)
}
timeout := time.After(1 * time.Second)
select {
case <-timeout:
t.Fatal("timed out waiting for server to finish")
case <-lisDone:
}
return client
}
func createTempFile(t *testing.T, body []byte) string {
tmpFile, err := os.CreateTemp(os.TempDir(), "go-unit-*.tmp")
if err != nil {
t.Fatal(err)
}
tmpFile.Close()
if err = os.WriteFile(tmpFile.Name(), body, os.ModePerm); err != nil {
t.Fatal(err)
}
return tmpFile.Name()
}

View File

@@ -81,7 +81,7 @@ func (pe *PeriodicalExecutor) Flush() bool {
}())
}
// Sync lets caller to run fn thread-safe with pe, especially for the underlying container.
// Sync lets caller run fn thread-safe with pe, especially for the underlying container.
func (pe *PeriodicalExecutor) Sync(fn func()) {
pe.lock.Lock()
defer pe.lock.Unlock()
@@ -116,7 +116,7 @@ func (pe *PeriodicalExecutor) addAndCheck(task any) (any, bool) {
}
func (pe *PeriodicalExecutor) backgroundFlush() {
threading.GoSafe(func() {
go func() {
// flush before quit goroutine to avoid missing tasks
defer pe.Flush()
@@ -144,7 +144,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
}
}
}
})
}()
}
func (pe *PeriodicalExecutor) doneExecution() {
@@ -162,7 +162,9 @@ func (pe *PeriodicalExecutor) executeTasks(tasks any) bool {
ok := pe.hasTasks(tasks)
if ok {
pe.container.Execute(tasks)
threading.RunSafe(func() {
pe.container.Execute(tasks)
})
}
return ok

View File

@@ -108,6 +108,64 @@ func TestPeriodicalExecutor_Bulk(t *testing.T) {
lock.Unlock()
}
func TestPeriodicalExecutor_Panic(t *testing.T) {
// avoid data race
var lock sync.Mutex
ticker := timex.NewFakeTicker()
var (
executedTasks []int
expected []int
)
executor := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, func(tasks any) {
tt := tasks.([]int)
lock.Lock()
executedTasks = append(executedTasks, tt...)
lock.Unlock()
if tt[0] == 0 {
panic("test")
}
}))
executor.newTicker = func(duration time.Duration) timex.Ticker {
return ticker
}
for i := 0; i < 30; i++ {
executor.Add(i)
expected = append(expected, i)
}
ticker.Tick()
ticker.Tick()
time.Sleep(time.Millisecond)
lock.Lock()
assert.Equal(t, expected, executedTasks)
lock.Unlock()
}
func TestPeriodicalExecutor_FlushPanic(t *testing.T) {
var (
executedTasks []int
expected []int
lock sync.Mutex
)
executor := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, func(tasks any) {
tt := tasks.([]int)
lock.Lock()
executedTasks = append(executedTasks, tt...)
lock.Unlock()
if tt[0] == 0 {
panic("flush panic")
}
}))
for i := 0; i < 8; i++ {
executor.Add(i)
expected = append(expected, i)
}
executor.Flush()
lock.Lock()
assert.Equal(t, expected, executedTasks)
lock.Unlock()
}
func TestPeriodicalExecutor_Wait(t *testing.T) {
var lock sync.Mutex
executer := NewBulkExecutor(func(tasks []any) {
@@ -151,13 +209,7 @@ func TestPeriodicalExecutor_Deadlock(t *testing.T) {
}
func TestPeriodicalExecutor_hasTasks(t *testing.T) {
ticker := timex.NewFakeTicker()
defer ticker.Stop()
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, nil))
exec.newTicker = func(d time.Duration) timex.Ticker {
return ticker
}
assert.False(t, exec.hasTasks(nil))
assert.True(t, exec.hasTasks(1))
}

View File

@@ -1,5 +1,4 @@
//go:build windows
// +build windows
package fs

View File

@@ -1,5 +1,4 @@
//go:build linux || darwin
// +build linux darwin
package fs

View File

@@ -9,21 +9,6 @@ import (
"github.com/zeromicro/go-zero/core/stores/redis"
)
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
const periodScript = `local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call("INCRBY", KEYS[1], 1)
if current == 1 then
redis.call("expire", KEYS[1], window)
end
if current < limit then
return 1
elseif current == limit then
return 2
else
return 0
end`
const (
// Unknown means not initialized state.
Unknown = iota
@@ -39,8 +24,25 @@ const (
internalHitQuota = 2
)
// ErrUnknownCode is an error that represents unknown status code.
var ErrUnknownCode = errors.New("unknown status code")
var (
// ErrUnknownCode is an error that represents unknown status code.
ErrUnknownCode = errors.New("unknown status code")
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
periodScript = redis.NewScript(`local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call("INCRBY", KEYS[1], 1)
if current == 1 then
redis.call("expire", KEYS[1], window)
end
if current < limit then
return 1
elseif current == limit then
return 2
else
return 0
end`)
)
type (
// PeriodOption defines the method to customize a PeriodLimit.
@@ -80,7 +82,7 @@ func (h *PeriodLimit) Take(key string) (int, error) {
// TakeCtx requests a permit with context, it returns the permit state.
func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
resp, err := h.limitStore.EvalCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
resp, err := h.limitStore.ScriptRunCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
strconv.Itoa(h.quota),
strconv.Itoa(h.calcExpireSeconds()),
})

View File

@@ -33,9 +33,7 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
}
func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
const (
seconds = 1

View File

@@ -15,10 +15,15 @@ import (
)
const (
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
// KEYS[1] as tokens_key
// KEYS[2] as timestamp_key
script = `local rate = tonumber(ARGV[1])
tokenFormat = "{%s}.tokens"
timestampFormat = "{%s}.ts"
pingInterval = time.Millisecond * 100
)
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
// KEYS[1] as tokens_key
// KEYS[2] as timestamp_key
var script = redis.NewScript(`local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
@@ -45,11 +50,7 @@ end
redis.call("setex", KEYS[1], ttl, new_tokens)
redis.call("setex", KEYS[2], ttl, now)
return allowed`
tokenFormat = "{%s}.tokens"
timestampFormat = "{%s}.ts"
pingInterval = time.Millisecond * 100
)
return allowed`)
// A TokenLimiter controls how frequently events are allowed to happen with in one second.
type TokenLimiter struct {
@@ -110,7 +111,7 @@ func (lim *TokenLimiter) reserveN(ctx context.Context, now time.Time, n int) boo
return lim.rescueLimiter.AllowN(now, n)
}
resp, err := lim.store.EvalCtx(ctx,
resp, err := lim.store.ScriptRunCtx(ctx,
script,
[]string{
lim.tokenKey,

View File

@@ -70,9 +70,7 @@ func TestTokenLimit_Rescue(t *testing.T) {
}
func TestTokenLimit_Take(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
const (
total = 100
@@ -92,9 +90,7 @@ func TestTokenLimit_Take(t *testing.T) {
}
func TestTokenLimit_TakeBurst(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
const (
total = 100

View File

@@ -23,7 +23,7 @@ type LogConf struct {
MaxContentLength uint32 `json:",optional"`
// Compress represents whether to compress the log file, default is `false`.
Compress bool `json:",optional"`
// Stdout represents whether to log statistics, default is `true`.
// Stat represents whether to log statistics, default is `true`.
Stat bool `json:",default=true"`
// KeepDays represents how many days the log files will be kept. Default to keep all files.
// Only take effect when Mode is `file` or `volume`, both work when Rotation is `daily` or `size`.
@@ -38,7 +38,7 @@ type LogConf struct {
// MaxSize represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`.
// Only take effect when RotationRuleType is `size`
MaxSize int `json:",default=0"`
// RotationRuleType represents the type of log rotation rule. Default is `daily`.
// Rotation represents the type of log rotation rule. Default is `daily`.
// daily: daily rotation.
// size: size limited rotation.
Rotation string `json:",default=daily,options=[daily,size]"`

View File

@@ -197,7 +197,12 @@ func Must(err error) {
msg := err.Error()
log.Print(msg)
getWriter().Severe(msg)
os.Exit(1)
if ExitOnFatal.True() {
os.Exit(1)
} else {
panic(msg)
}
}
// MustSetup sets up logging with given config c. It exits on error.

View File

@@ -24,6 +24,10 @@ var (
_ Writer = (*mockWriter)(nil)
)
func init() {
ExitOnFatal.Set(false)
}
type mockWriter struct {
lock sync.Mutex
builder strings.Builder
@@ -208,6 +212,12 @@ func TestFileLineConsoleMode(t *testing.T) {
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
}
func TestMust(t *testing.T) {
assert.Panics(t, func() {
Must(errors.New("foo"))
})
}
func TestStructedLogAlert(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
@@ -574,26 +584,38 @@ func TestSetup(t *testing.T) {
atomic.StoreUint32(&encoding, jsonEncodingType)
}()
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
Encoding: "json",
TimeFormat: timeFormat,
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
TimeFormat: timeFormat,
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "file",
Path: os.TempDir(),
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "volume",
Path: os.TempDir(),
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
TimeFormat: timeFormat,
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",

View File

@@ -237,7 +237,7 @@ func NewLogger(filename string, rule RotateRule, compress bool) (*RotateLogger,
rule: rule,
compress: compress,
}
if err := l.init(); err != nil {
if err := l.initialize(); err != nil {
return nil, err
}
@@ -281,7 +281,7 @@ func (l *RotateLogger) getBackupFilename() string {
return l.backup
}
func (l *RotateLogger) init() error {
func (l *RotateLogger) initialize() error {
l.backup = l.rule.BackupFileName()
if fileInfo, err := os.Stat(l.filename); err != nil {

View File

@@ -1,6 +1,10 @@
package logx
import "errors"
import (
"errors"
"github.com/zeromicro/go-zero/core/syncx"
)
const (
// DebugLevel logs everything
@@ -61,6 +65,8 @@ var (
ErrLogPathNotSet = errors.New("log path must be set")
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
ErrLogServiceNameNotSet = errors.New("log service name must be set")
// ExitOnFatal defines whether to exit on fatal errors, defined here to make it easier to test.
ExitOnFatal = syncx.ForAtomicBool(true)
truncatedField = Field(truncatedKey, true)
)

View File

@@ -289,6 +289,10 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any)
return reflect.ValueOf(mapValue), nil
}
if keyType != valueType.Key() {
return emptyValue, errTypeMismatch
}
refValue := reflect.ValueOf(mapValue)
targetValue := reflect.MakeMapWithSize(mapType, refValue.Len())
dereffedElemType := Deref(elemType)
@@ -691,6 +695,10 @@ func (u *Unmarshaler) processFieldWithEnvValue(fieldType reflect.Type, value ref
func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value,
m valuerWithParent, fullName string) error {
if !field.IsExported() {
return nil
}
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
if err != nil {
return err
@@ -810,6 +818,11 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu
}
if u.opts.fillDefault {
if fieldType.Kind() != reflect.Ptr && fieldKind == reflect.Struct {
return u.processFieldNotFromString(fieldType, value, valueWithParent{
value: emptyMap,
}, opts, fullName)
}
return nil
}
@@ -865,12 +878,9 @@ func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName
numFields := baseType.NumField()
for i := 0; i < numFields; i++ {
field := baseType.Field(i)
if !field.IsExported() {
continue
}
if err := u.processField(field, valElem.Field(i), m, fullName); err != nil {
typeField := baseType.Field(i)
valueField := valElem.Field(i)
if err := u.processField(typeField, valueField, m, fullName); err != nil {
return err
}
}

View File

@@ -53,6 +53,52 @@ func TestUnmarshalWithoutTagName(t *testing.T) {
}
}
func TestUnmarshalWithLowerField(t *testing.T) {
type (
Lower struct {
value int `key:"lower"`
}
inner struct {
Lower
Optional bool `key:",optional"`
}
)
m := map[string]any{
"Optional": true,
"lower": 1,
}
var in inner
if assert.NoError(t, UnmarshalKey(m, &in)) {
assert.True(t, in.Optional)
assert.Equal(t, 0, in.value)
}
}
func TestUnmarshalWithLowerAnonymousStruct(t *testing.T) {
type (
lower struct {
Value int `key:"lower"`
}
inner struct {
lower
Optional bool `key:",optional"`
}
)
m := map[string]any{
"Optional": true,
"lower": 1,
}
var in inner
if assert.NoError(t, UnmarshalKey(m, &in)) {
assert.True(t, in.Optional)
assert.Equal(t, 1, in.Value)
}
}
func TestUnmarshalWithoutTagNameWithCanonicalKey(t *testing.T) {
type inner struct {
Name string `key:"name"`
@@ -4285,6 +4331,97 @@ func TestUnmarshalOnlyPublicVariables(t *testing.T) {
}
}
func TestFillDefaultUnmarshal(t *testing.T) {
fillDefaultUnmarshal := NewUnmarshaler(jsonTagKey, WithDefault())
t.Run("nil", func(t *testing.T) {
type St struct{}
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, St{})
assert.Error(t, err)
})
t.Run("not nil", func(t *testing.T) {
type St struct{}
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &St{})
assert.NoError(t, err)
})
t.Run("default", func(t *testing.T) {
type St struct {
A string `json:",default=a"`
B string
}
var st St
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
assert.NoError(t, err)
assert.Equal(t, "a", st.A)
})
t.Run("env", func(t *testing.T) {
type St struct {
A string `json:",default=a"`
B string
C string `json:",env=TEST_C"`
}
t.Setenv("TEST_C", "c")
var st St
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
assert.NoError(t, err)
assert.Equal(t, "a", st.A)
assert.Equal(t, "c", st.C)
})
t.Run("has value", func(t *testing.T) {
type St struct {
A string `json:",default=a"`
B string
}
var st = St{
A: "b",
}
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
assert.Error(t, err)
})
t.Run("handling struct", func(t *testing.T) {
type St struct {
A string `json:",default=a"`
B string
}
type St2 struct {
St
St1 St
St3 *St
C string `json:",default=c"`
D string
Child *St2
}
var st2 St2
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st2)
assert.NoError(t, err)
assert.Equal(t, "a", st2.St.A)
assert.Equal(t, "a", st2.St1.A)
assert.Nil(t, st2.St3)
assert.Equal(t, "c", st2.C)
assert.Nil(t, st2.Child)
})
}
func Test_UnmarshalMap(t *testing.T) {
type Customer struct {
Names map[int]string `key:"names"`
}
input := map[string]any{
"names": map[string]any{
"19": "Tom",
},
}
var customer Customer
assert.ErrorIs(t, UnmarshalKey(input, &customer), errTypeMismatch)
}
func BenchmarkDefaultValue(b *testing.B) {
for i := 0; i < b.N; i++ {
var a struct {
@@ -4384,56 +4521,3 @@ func BenchmarkUnmarshal(b *testing.B) {
UnmarshalKey(data, &an)
}
}
func TestFillDefaultUnmarshal(t *testing.T) {
fillDefaultUnmarshal := NewUnmarshaler(jsonTagKey, WithDefault())
t.Run("nil", func(t *testing.T) {
type St struct{}
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, St{})
assert.Error(t, err)
})
t.Run("not nil", func(t *testing.T) {
type St struct{}
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &St{})
assert.NoError(t, err)
})
t.Run("default", func(t *testing.T) {
type St struct {
A string `json:",default=a"`
B string
}
var st St
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
assert.NoError(t, err)
assert.Equal(t, st.A, "a")
})
t.Run("env", func(t *testing.T) {
type St struct {
A string `json:",default=a"`
B string
C string `json:",env=TEST_C"`
}
t.Setenv("TEST_C", "c")
var st St
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
assert.NoError(t, err)
assert.Equal(t, st.A, "a")
assert.Equal(t, st.C, "c")
})
t.Run("has value", func(t *testing.T) {
type St struct {
A string `json:",default=a"`
B string
}
var st = St{
A: "b",
}
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
assert.Error(t, err)
})
}

View File

@@ -255,7 +255,7 @@ func parseGroupedSegments(val string) []string {
// don't modify returned fieldOptions, it's cached and shared among different calls.
func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fieldOptions, error) {
value := field.Tag.Get(tagName)
value := strings.TrimSpace(field.Tag.Get(tagName))
if len(value) == 0 {
return field.Name, nil, nil
}

View File

@@ -144,6 +144,10 @@ func TestParseSegments(t *testing.T) {
input: "",
expect: []string{},
},
{
input: " ",
expect: []string{},
},
{
input: ",",
expect: []string{""},

View File

@@ -34,7 +34,7 @@ type (
recursiveValuer node
)
// Value gets the value assciated with the given key from mv.
// Value gets the value associated with the given key from mv.
func (mv mapValuer) Value(key string) (any, bool) {
v, ok := mv[key]
return v, ok

View File

@@ -1,5 +1,4 @@
//go:build fuzz
// +build fuzz
package mr

View File

@@ -1,5 +1,4 @@
//go:build windows
// +build windows
package proc

View File

@@ -1,5 +1,4 @@
//go:build linux || darwin
// +build linux darwin
package proc

View File

@@ -1,5 +1,4 @@
//go:build windows
// +build windows
package proc

View File

@@ -1,5 +1,4 @@
//go:build linux || darwin
// +build linux darwin
package proc

View File

@@ -1,5 +1,4 @@
//go:build windows
// +build windows
package proc

View File

@@ -1,5 +1,4 @@
//go:build linux || darwin
// +build linux darwin
package proc

View File

@@ -1,5 +1,4 @@
//go:build linux || darwin
// +build linux darwin
package proc

View File

@@ -1,5 +1,4 @@
//go:build windows
// +build windows
package proc

View File

@@ -1,5 +1,4 @@
//go:build linux || darwin
// +build linux darwin
package proc

View File

@@ -1,5 +1,4 @@
//go:build debug
// +build debug
package search

View File

@@ -1,5 +1,4 @@
//go:build !linux
// +build !linux
package stat

View File

@@ -1,5 +1,4 @@
//go:build linux
// +build linux
package stat

View File

@@ -1,5 +1,4 @@
//go:build linux
// +build linux
package stat

View File

@@ -278,10 +278,8 @@ func runningInUserNS() bool {
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.
*/
// 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
}

View File

@@ -1,5 +1,4 @@
//go:build !linux
// +build !linux
package internal

View File

@@ -6,11 +6,9 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
)
func TestMetrics(t *testing.T) {
logx.Disable()
DisableLog()
defer logEnabled.Set(true)

64
core/stat/usage_test.go Normal file
View File

@@ -0,0 +1,64 @@
package stat
import (
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
)
func TestBToMb(t *testing.T) {
tests := []struct {
name string
bytes uint64
expected float32
}{
{
name: "Test 1: Convert 0 bytes to MB",
bytes: 0,
expected: 0,
},
{
name: "Test 2: Convert 1048576 bytes to MB",
bytes: 1048576,
expected: 1,
},
{
name: "Test 3: Convert 2097152 bytes to MB",
bytes: 2097152,
expected: 2,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := bToMb(test.bytes)
assert.Equal(t, test.expected, result)
})
}
}
func TestPrintUsage(t *testing.T) {
var buf bytes.Buffer
writer := logx.NewWriter(&buf)
old := logx.Reset()
logx.SetWriter(writer)
defer logx.SetWriter(old)
printUsage()
output := buf.String()
assert.Contains(t, output, "CPU:")
assert.Contains(t, output, "MEMORY:")
assert.Contains(t, output, "Alloc=")
assert.Contains(t, output, "TotalAlloc=")
assert.Contains(t, output, "Sys=")
assert.Contains(t, output, "NumGC=")
lines := strings.Split(output, "\n")
assert.Len(t, lines, 2)
fields := strings.Split(lines[0], ", ")
assert.Len(t, fields, 5)
}

View File

@@ -112,12 +112,8 @@ func (mc *mockedNode) TakeWithExpireCtx(ctx context.Context, val any, key string
func TestCache_SetDel(t *testing.T) {
t.Run("test set del", func(t *testing.T) {
const total = 1000
r1, clean1, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean1()
r2, clean2, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean2()
r1 := redistest.CreateRedis(t)
r2 := redistest.CreateRedis(t)
conf := ClusterConf{
{
RedisConf: redis.RedisConf{
@@ -193,9 +189,7 @@ func TestCache_SetDel(t *testing.T) {
func TestCache_OneNode(t *testing.T) {
const total = 1000
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
conf := ClusterConf{
{
RedisConf: redis.RedisConf{

View File

@@ -34,10 +34,8 @@ func init() {
func TestCacheNode_DelCache(t *testing.T) {
t.Run("del cache", func(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
store := redistest.CreateRedis(t)
store.Type = redis.ClusterType
defer clean()
cn := cacheNode{
rds: store,
@@ -84,9 +82,7 @@ func TestCacheNode_DelCache(t *testing.T) {
}
func TestCacheNode_DelCacheWithErrors(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
store.Type = redis.ClusterType
cn := cacheNode{
@@ -122,9 +118,7 @@ func TestCacheNode_InvalidCache(t *testing.T) {
}
func TestCacheNode_SetWithExpire(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
cn := cacheNode{
rds: store,
@@ -139,14 +133,12 @@ func TestCacheNode_SetWithExpire(t *testing.T) {
}
func TestCacheNode_Take(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
cn := NewNode(store, syncx.NewSingleFlight(), NewStat("any"), errTestNotFound,
WithExpiry(time.Second), WithNotFoundExpiry(time.Second))
var str string
err = cn.Take(&str, "any", func(v any) error {
err := cn.Take(&str, "any", func(v any) error {
*v.(*string) = "value"
return nil
})
@@ -174,9 +166,7 @@ func TestCacheNode_TakeBadRedis(t *testing.T) {
}
func TestCacheNode_TakeNotFound(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
cn := cacheNode{
rds: store,
@@ -188,7 +178,7 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
errNotFound: errTestNotFound,
}
var str string
err = cn.Take(&str, "any", func(v any) error {
err := cn.Take(&str, "any", func(v any) error {
return errTestNotFound
})
assert.True(t, cn.IsNotFound(err))
@@ -213,9 +203,7 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
}
func TestCacheNode_TakeNotFoundButChangedByOthers(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.NoError(t, err)
defer clean()
store := redistest.CreateRedis(t)
cn := cacheNode{
rds: store,
@@ -228,7 +216,7 @@ func TestCacheNode_TakeNotFoundButChangedByOthers(t *testing.T) {
}
var str string
err = cn.Take(&str, "any", func(v any) error {
err := cn.Take(&str, "any", func(v any) error {
store.Set("any", "foo")
return errTestNotFound
})
@@ -242,9 +230,7 @@ func TestCacheNode_TakeNotFoundButChangedByOthers(t *testing.T) {
}
func TestCacheNode_TakeWithExpire(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
cn := cacheNode{
rds: store,
@@ -256,7 +242,7 @@ func TestCacheNode_TakeWithExpire(t *testing.T) {
errNotFound: errors.New("any"),
}
var str string
err = cn.TakeWithExpire(&str, "any", func(v any, expire time.Duration) error {
err := cn.TakeWithExpire(&str, "any", func(v any, expire time.Duration) error {
*v.(*string) = "value"
return nil
})
@@ -269,9 +255,7 @@ func TestCacheNode_TakeWithExpire(t *testing.T) {
}
func TestCacheNode_String(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
cn := cacheNode{
rds: store,
@@ -286,9 +270,7 @@ func TestCacheNode_String(t *testing.T) {
}
func TestCacheValueWithBigInt(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redistest.CreateRedis(t)
cn := cacheNode{
rds: store,

View File

@@ -2,7 +2,8 @@ package postgres
import (
// imports the driver, don't remove this comment, golint requires.
_ "github.com/jackc/pgx/v5"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)

View File

@@ -1,6 +1,9 @@
package redis
import "errors"
import (
"errors"
"time"
)
var (
// ErrEmptyHost is an error that indicates no redis host is set.
@@ -9,23 +12,24 @@ var (
ErrEmptyType = errors.New("empty redis type")
// ErrEmptyKey is an error that indicates no redis key is set.
ErrEmptyKey = errors.New("empty redis key")
// ErrPing is an error that indicates ping failed.
ErrPing = errors.New("ping redis failed")
)
type (
// A RedisConf is a redis config.
RedisConf struct {
Host string
Type string `json:",default=node,options=node|cluster"`
Pass string `json:",optional"`
Tls bool `json:",optional"`
Host string
Type string `json:",default=node,options=node|cluster"`
Pass string `json:",optional"`
Tls bool `json:",optional"`
NonBlock bool `json:",default=true"`
// PingTimeout is the timeout for ping redis.
PingTimeout time.Duration `json:",default=1s"`
}
// A RedisKeyConf is a redis config with key.
RedisKeyConf struct {
RedisConf
Key string `json:",optional"`
Key string
}
)

View File

@@ -10,6 +10,7 @@ import (
red "github.com/go-redis/redis/v8"
"github.com/zeromicro/go-zero/core/breaker"
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/core/syncx"
)
@@ -25,6 +26,7 @@ const (
blockingQueryTimeout = 5 * time.Second
readWriteTimeout = 2 * time.Second
defaultSlowThreshold = time.Millisecond * 100
defaultPingTimeout = time.Second
)
var (
@@ -51,11 +53,12 @@ type (
// Redis defines a redis node/cluster. It is thread-safe.
Redis struct {
Addr string
Type string
Pass string
tls bool
brk breaker.Breaker
Addr string
Type string
Pass string
tls bool
brk breaker.Breaker
hooks []red.Hook
}
// RedisNode interface represents a redis node.
@@ -84,6 +87,8 @@ type (
FloatCmd = red.FloatCmd
// StringCmd is an alias of redis.StringCmd.
StringCmd = red.StringCmd
// Script is an alias of redis.Script.
Script = red.Script
)
// New returns a Redis with given options.
@@ -119,8 +124,10 @@ func NewRedis(conf RedisConf, opts ...Option) (*Redis, error) {
}
rds := newRedis(conf.Host, opts...)
if !rds.Ping() {
return nil, ErrPing
if !conf.NonBlock {
if err := rds.checkConnection(conf.PingTimeout); err != nil {
return nil, errorx.Wrap(err, fmt.Sprintf("redis connect error, addr: %s", conf.Host))
}
}
return rds, nil
@@ -140,6 +147,11 @@ func newRedis(addr string, opts ...Option) *Redis {
return r
}
// NewScript returns a new Script instance.
func NewScript(script string) *Script {
return red.NewScript(script)
}
// BitCount is redis bitcount command implementation.
func (s *Redis) BitCount(key string, start, end int64) (int64, error) {
return s.BitCountCtx(context.Background(), key, start, end)
@@ -832,12 +844,12 @@ func (s *Redis) HincrbyCtx(ctx context.Context, key, field string, increment int
return
}
// HincrbyFloat is the implementation of redis hincrby command.
// HincrbyFloat is the implementation of redis hincrbyfloat command.
func (s *Redis) HincrbyFloat(key, field string, increment float64) (float64, error) {
return s.HincrbyFloatCtx(context.Background(), key, field, increment)
}
// HincrbyFloatCtx is the implementation of redis hincrby command.
// HincrbyFloatCtx is the implementation of redis hincrbyfloat command.
func (s *Redis) HincrbyFloatCtx(ctx context.Context, key, field string, increment float64) (val float64, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
@@ -1065,12 +1077,12 @@ func (s *Redis) IncrbyCtx(ctx context.Context, key string, increment int64) (val
return
}
// IncrbyFloat is the implementation of redis incrby command.
// IncrbyFloat is the implementation of redis hincrbyfloat command.
func (s *Redis) IncrbyFloat(key string, increment float64) (float64, error) {
return s.IncrbyFloatCtx(context.Background(), key, increment)
}
// IncrbyFloatCtx is the implementation of redis incrby command.
// IncrbyFloatCtx is the implementation of redis hincrbyfloat command.
func (s *Redis) IncrbyFloatCtx(ctx context.Context, key string, increment float64) (val float64, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
@@ -1170,6 +1182,26 @@ func (s *Redis) LpopCtx(ctx context.Context, key string) (val string, err error)
return
}
// LpopCount is the implementation of redis lpopCount command.
func (s *Redis) LpopCount(key string, count int) ([]string, error) {
return s.LpopCountCtx(context.Background(), key, count)
}
// LpopCountCtx is the implementation of redis lpopCount command.
func (s *Redis) LpopCountCtx(ctx context.Context, key string, count int) (val []string, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
val, err = conn.LPopCount(ctx, key, count).Result()
return err
}, acceptable)
return
}
// Lpush is the implementation of redis lpush command.
func (s *Redis) Lpush(key string, values ...any) (int, error) {
return s.LpushCtx(context.Background(), key, values...)
@@ -1432,6 +1464,26 @@ func (s *Redis) RpopCtx(ctx context.Context, key string) (val string, err error)
return
}
// RpopCount is the implementation of redis rpopCount command.
func (s *Redis) RpopCount(key string, count int) ([]string, error) {
return s.RpopCountCtx(context.Background(), key, count)
}
// RpopCountCtx is the implementation of redis rpopCount command.
func (s *Redis) RpopCountCtx(ctx context.Context, key string, count int) (val []string, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
val, err = conn.RPopCount(ctx, key, count).Result()
return err
}, acceptable)
return
}
// Rpush is the implementation of redis rpush command.
func (s *Redis) Rpush(key string, values ...any) (int, error) {
return s.RpushCtx(context.Background(), key, values...)
@@ -1585,6 +1637,25 @@ func (s *Redis) ScriptLoadCtx(ctx context.Context, script string) (string, error
return conn.ScriptLoad(ctx, script).Result()
}
// ScriptRun is the implementation of *redis.Script run command.
func (s *Redis) ScriptRun(script *Script, keys []string, args ...any) (any, error) {
return s.ScriptRunCtx(context.Background(), script, keys, args...)
}
// ScriptRunCtx is the implementation of *redis.Script run command.
func (s *Redis) ScriptRunCtx(ctx context.Context, script *Script, keys []string, args ...any) (val any, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
val, err = script.Run(ctx, conn, keys, args...).Result()
return err
}, acceptable)
return
}
// Set is the implementation of redis set command.
func (s *Redis) Set(key, value string) error {
return s.SetCtx(context.Background(), key, value)
@@ -2729,6 +2800,23 @@ func (s *Redis) ZunionstoreCtx(ctx context.Context, dest string, store *ZStore)
return
}
func (s *Redis) checkConnection(pingTimeout time.Duration) error {
conn, err := getRedis(s)
if err != nil {
return err
}
timeout := defaultPingTimeout
if pingTimeout > 0 {
timeout = pingTimeout
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return conn.Ping(ctx).Err()
}
// Cluster customizes the given Redis as a cluster.
func Cluster() Option {
return func(r *Redis) {
@@ -2755,6 +2843,14 @@ func WithTLS() Option {
}
}
// withHook customizes the given Redis with given hook, only for private use now,
// maybe expose later.
func withHook(hook red.Hook) Option {
return func(r *Redis) {
r.hooks = append(r.hooks, hook)
}
}
func acceptable(err error) bool {
return err == nil || err == red.Nil || err == context.Canceled
}

View File

@@ -16,6 +16,25 @@ import (
"github.com/zeromicro/go-zero/core/stringx"
)
type myHook struct {
red.Hook
includePing bool
}
var _ red.Hook = myHook{}
func (m myHook) BeforeProcess(ctx context.Context, cmd red.Cmder) (context.Context, error) {
return ctx, nil
}
func (m myHook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
// skip ping cmd
if cmd.Name() == "ping" && !m.includePing {
return nil
}
return errors.New("hook error")
}
func TestNewRedis(t *testing.T) {
r1, err := miniredis.Run()
assert.NoError(t, err)
@@ -126,6 +145,31 @@ func TestNewRedis(t *testing.T) {
}
}
func TestRedis_NonBlock(t *testing.T) {
logx.Disable()
t.Run("nonBlock true", func(t *testing.T) {
s := miniredis.RunT(t)
// use hook to simulate redis ping error
_, err := NewRedis(RedisConf{
Host: s.Addr(),
NonBlock: true,
Type: NodeType,
}, withHook(myHook{includePing: true}))
assert.NoError(t, err)
})
t.Run("nonBlock false", func(t *testing.T) {
s := miniredis.RunT(t)
_, err := NewRedis(RedisConf{
Host: s.Addr(),
NonBlock: false,
Type: NodeType,
}, withHook(myHook{includePing: true}))
assert.ErrorContains(t, err, "redis connect error")
})
}
func TestRedis_Decr(t *testing.T) {
runOnRedis(t, func(client *Redis) {
_, err := New(client.Addr, badType()).Decr("a")
@@ -196,6 +240,24 @@ func TestRedis_Eval(t *testing.T) {
})
}
func TestRedis_ScriptRun(t *testing.T) {
runOnRedis(t, func(client *Redis) {
sc := NewScript(`redis.call("EXISTS", KEYS[1])`)
sc2 := NewScript(`return redis.call("EXISTS", KEYS[1])`)
_, err := New(client.Addr, badType()).ScriptRun(sc, []string{"notexist"})
assert.NotNil(t, err)
_, err = client.ScriptRun(sc, []string{"notexist"})
assert.Equal(t, Nil, err)
err = client.Set("key1", "value1")
assert.Nil(t, err)
_, err = client.ScriptRun(sc, []string{"key1"})
assert.Equal(t, Nil, err)
val, err := client.ScriptRun(sc2, []string{"key1"})
assert.Nil(t, err)
assert.Equal(t, int64(1), val)
})
}
func TestRedis_GeoHash(t *testing.T) {
runOnRedis(t, func(client *Redis) {
_, err := client.GeoHash("parent", "child1", "child2")
@@ -507,6 +569,14 @@ func TestRedis_List(t *testing.T) {
vals, err = client.Lrange("key", 0, 10)
assert.Nil(t, err)
assert.EqualValues(t, []string{"value2", "value3"}, vals)
vals, err = client.LpopCount("key", 2)
assert.Nil(t, err)
assert.EqualValues(t, []string{"value2", "value3"}, vals)
_, err = client.Lpush("key", "value1", "value2")
assert.Nil(t, err)
vals, err = client.RpopCount("key", 4)
assert.Nil(t, err)
assert.EqualValues(t, []string{"value1", "value2"}, vals)
})
})
@@ -523,6 +593,34 @@ func TestRedis_List(t *testing.T) {
_, err = client.Rpush("key", "value3", "value4")
assert.Error(t, err)
_, err = client.LpopCount("key", 2)
assert.Error(t, err)
_, err = client.RpopCount("key", 2)
assert.Error(t, err)
})
})
t.Run("list redis type error", func(t *testing.T) {
runOnRedisWithError(t, func(client *Redis) {
client.Type = "nil"
_, err := client.Llen("key")
assert.Error(t, err)
_, err = client.Lpush("key", "value1", "value2")
assert.Error(t, err)
_, err = client.Lrem("key", 2, "value1")
assert.Error(t, err)
_, err = client.Rpush("key", "value3", "value4")
assert.Error(t, err)
_, err = client.LpopCount("key", 2)
assert.Error(t, err)
_, err = client.RpopCount("key", 2)
assert.Error(t, err)
})
})
}

View File

@@ -31,7 +31,7 @@ func CreateBlockingNode(r *Redis) (ClosableNode, error) {
return &clientBridge{client}, nil
case ClusterType:
client := red.NewClusterClient(&red.ClusterOptions{
Addrs: []string{r.Addr},
Addrs: splitClusterAddrs(r.Addr),
Password: r.Pass,
MaxRetries: maxRetries,
PoolSize: 1,

View File

@@ -33,6 +33,9 @@ func getClient(r *Redis) (*red.Client, error) {
TLSConfig: tlsConfig,
})
store.AddHook(durationHook)
for _, hook := range r.hooks {
store.AddHook(hook)
}
return store, nil
})

View File

@@ -3,11 +3,14 @@ package redis
import (
"crypto/tls"
"io"
"strings"
red "github.com/go-redis/redis/v8"
"github.com/zeromicro/go-zero/core/syncx"
)
const addrSep = ","
var clusterManager = syncx.NewResourceManager()
func getCluster(r *Redis) (*red.ClusterClient, error) {
@@ -19,13 +22,16 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
}
}
store := red.NewClusterClient(&red.ClusterOptions{
Addrs: []string{r.Addr},
Addrs: splitClusterAddrs(r.Addr),
Password: r.Pass,
MaxRetries: maxRetries,
MinIdleConns: idleConns,
TLSConfig: tlsConfig,
})
store.AddHook(durationHook)
for _, hook := range r.hooks {
store.AddHook(hook)
}
return store, nil
})
@@ -35,3 +41,18 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
return val.(*red.ClusterClient), nil
}
func splitClusterAddrs(addr string) []string {
addrs := strings.Split(addr, addrSep)
unique := make(map[string]struct{})
for _, each := range addrs {
unique[strings.TrimSpace(each)] = struct{}{}
}
addrs = addrs[:0]
for k := range unique {
addrs = append(addrs, k)
}
return addrs
}

View File

@@ -0,0 +1,43 @@
package redis
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitClusterAddrs(t *testing.T) {
testCases := []struct {
name string
input string
expected []string
}{
{
name: "empty input",
input: "",
expected: []string{""},
},
{
name: "single address",
input: "127.0.0.1:8000",
expected: []string{"127.0.0.1:8000"},
},
{
name: "multiple addresses with duplicates",
input: "127.0.0.1:8000,127.0.0.1:8001, 127.0.0.1:8000",
expected: []string{"127.0.0.1:8000", "127.0.0.1:8001"},
},
{
name: "multiple addresses without duplicates",
input: "127.0.0.1:8000, 127.0.0.1:8001, 127.0.0.1:8002",
expected: []string{"127.0.0.1:8000", "127.0.0.1:8001", "127.0.0.1:8002"},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
assert.ElementsMatch(t, tc.expected, splitClusterAddrs(tc.input))
})
}
}

View File

@@ -17,17 +17,20 @@ const (
randomLen = 16
tolerance = 500 // milliseconds
millisPerSecond = 1000
lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
)
var (
lockScript = NewScript(`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
end`)
delScript = NewScript(`if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end`
end`)
)
// A RedisLock is a redis lock.
@@ -59,7 +62,7 @@ func (rl *RedisLock) Acquire() (bool, error) {
// AcquireCtx acquires the lock with the given ctx.
func (rl *RedisLock) AcquireCtx(ctx context.Context) (bool, error) {
seconds := atomic.LoadUint32(&rl.seconds)
resp, err := rl.store.EvalCtx(ctx, lockCommand, []string{rl.key}, []string{
resp, err := rl.store.ScriptRunCtx(ctx, lockScript, []string{rl.key}, []string{
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
})
if err == red.Nil {
@@ -87,7 +90,7 @@ func (rl *RedisLock) Release() (bool, error) {
// ReleaseCtx releases the lock with the given ctx.
func (rl *RedisLock) ReleaseCtx(ctx context.Context) (bool, error) {
resp, err := rl.store.EvalCtx(ctx, delCommand, []string{rl.key}, []string{rl.id})
resp, err := rl.store.ScriptRunCtx(ctx, delScript, []string{rl.key}, []string{rl.id})
if err != nil {
return false, err
}

View File

@@ -1,31 +1,20 @@
package redistest
import (
"time"
"testing"
"github.com/alicebob/miniredis/v2"
"github.com/zeromicro/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/stores/redis"
)
// CreateRedis returns an in process redis.Redis.
func CreateRedis() (r *redis.Redis, clean func(), err error) {
mr, err := miniredis.Run()
if err != nil {
return nil, nil, err
}
return redis.New(mr.Addr()), func() {
ch := make(chan lang.PlaceholderType)
go func() {
mr.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):
}
}, nil
func CreateRedis(t *testing.T) *redis.Redis {
r, _ := CreateRedisWithClean(t)
return r
}
// CreateRedisWithClean returns an in process redis.Redis and a clean function.
func CreateRedisWithClean(t *testing.T) (r *redis.Redis, clean func()) {
mr := miniredis.RunT(t)
return redis.New(mr.Addr()), mr.Close
}

View File

@@ -33,13 +33,11 @@ func init() {
func TestCachedConn_GetCache(t *testing.T) {
resetStats()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
var value string
err = c.GetCache("any", &value)
err := c.GetCache("any", &value)
assert.Equal(t, ErrNotFound, err)
r.Set("any", `"value"`)
err = c.GetCache("any", &value)
@@ -49,15 +47,13 @@ func TestCachedConn_GetCache(t *testing.T) {
func TestStat(t *testing.T) {
resetStats()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
for i := 0; i < 10; i++ {
var str string
err = c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v any) error {
err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v any) error {
*v.(*string) = "zero"
return nil
})
@@ -72,9 +68,7 @@ func TestStat(t *testing.T) {
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
resetStats()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
c := NewConn(dummySqlConn{}, cache.CacheConf{
{
@@ -87,7 +81,7 @@ func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
}, cache.WithExpiry(time.Second*10))
var str string
err = c.QueryRowIndex(&str, "index", func(s any) string {
err := c.QueryRowIndex(&str, "index", func(s any) string {
return fmt.Sprintf("%s/1234", s)
}, func(conn sqlx.SqlConn, v any) (any, error) {
*v.(*string) = "zero"
@@ -121,16 +115,14 @@ func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
resetStats()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
cache.WithNotFoundExpiry(time.Second))
var str string
r.Set("index", `"primary"`)
err = c.QueryRowIndex(&str, "index", func(s any) string {
err := c.QueryRowIndex(&str, "index", func(s any) string {
return fmt.Sprintf("%s/1234", s)
}, func(conn sqlx.SqlConn, v any) (any, error) {
assert.Fail(t, "should not go here")
@@ -211,16 +203,14 @@ func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
resetStats()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
cache.WithNotFoundExpiry(time.Second))
var str string
r.Set("index", test.primaryCache)
err = c.QueryRowIndex(&str, "index", func(s any) string {
err := c.QueryRowIndex(&str, "index", func(s any) string {
return fmt.Sprintf("%v/1234", s)
}, func(conn sqlx.SqlConn, v any) (any, error) {
assert.Fail(t, "should not go here")
@@ -251,16 +241,14 @@ func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
for k, v := range caches {
t.Run(k+"/"+v, func(t *testing.T) {
resetStats()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
cache.WithNotFoundExpiry(time.Second))
var str string
r.Set(k, v)
err = c.QueryRowIndex(&str, "index", func(s any) string {
err := c.QueryRowIndex(&str, "index", func(s any) string {
return fmt.Sprintf("%s/1234", s)
}, func(conn sqlx.SqlConn, v any) (any, error) {
*v.(*string) = "xin"
@@ -306,15 +294,13 @@ func TestStatCacheFails(t *testing.T) {
func TestStatDbFails(t *testing.T) {
resetStats()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
for i := 0; i < 20; i++ {
var str string
err = c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v any) error {
err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v any) error {
return errors.New("db failed")
})
assert.NotNil(t, err)
@@ -327,9 +313,7 @@ func TestStatDbFails(t *testing.T) {
func TestStatFromMemory(t *testing.T) {
resetStats()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
@@ -385,9 +369,7 @@ func TestStatFromMemory(t *testing.T) {
}
func TestCachedConnQueryRow(t *testing.T) {
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
const (
key = "user"
@@ -397,7 +379,7 @@ func TestCachedConnQueryRow(t *testing.T) {
var user string
var ran bool
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v any) error {
err := c.QueryRow(&user, key, func(conn sqlx.SqlConn, v any) error {
ran = true
user = value
return nil
@@ -413,9 +395,7 @@ func TestCachedConnQueryRow(t *testing.T) {
}
func TestCachedConnQueryRowFromCache(t *testing.T) {
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
const (
key = "user"
@@ -426,7 +406,7 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
var ran bool
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
assert.Nil(t, c.SetCache(key, value))
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v any) error {
err := c.QueryRow(&user, key, func(conn sqlx.SqlConn, v any) error {
ran = true
user = value
return nil
@@ -442,9 +422,7 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
}
func TestQueryRowNotFound(t *testing.T) {
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
const key = "user"
var conn trackedConn
@@ -452,7 +430,7 @@ func TestQueryRowNotFound(t *testing.T) {
var ran int
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
for i := 0; i < 20; i++ {
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v any) error {
err := c.QueryRow(&user, key, func(conn sqlx.SqlConn, v any) error {
ran++
return sql.ErrNoRows
})
@@ -462,13 +440,11 @@ func TestQueryRowNotFound(t *testing.T) {
}
func TestCachedConnExec(t *testing.T) {
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
var conn trackedConn
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
_, err := c.ExecNoCache("delete from user_table where id='kevin'")
assert.Nil(t, err)
assert.True(t, conn.execValue)
}
@@ -514,26 +490,22 @@ func TestCachedConnExecDropCacheFailed(t *testing.T) {
}
func TestCachedConnQueryRows(t *testing.T) {
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
var conn trackedConn
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
var users []string
err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'")
err := c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'")
assert.Nil(t, err)
assert.True(t, conn.queryRowsValue)
}
func TestCachedConnTransact(t *testing.T) {
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
var conn trackedConn
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
err = c.Transact(func(session sqlx.Session) error {
err := c.Transact(func(session sqlx.Session) error {
return nil
})
assert.Nil(t, err)
@@ -541,9 +513,7 @@ func TestCachedConnTransact(t *testing.T) {
}
func TestQueryRowNoCache(t *testing.T) {
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
const (
key = "user"
@@ -557,20 +527,18 @@ func TestQueryRowNoCache(t *testing.T) {
return nil
}}
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
err = c.QueryRowNoCache(&user, key)
err := c.QueryRowNoCache(&user, key)
assert.Nil(t, err)
assert.Equal(t, value, user)
assert.True(t, ran)
}
func TestNewConnWithCache(t *testing.T) {
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redistest.CreateRedis(t)
var conn trackedConn
c := NewConnWithCache(&conn, cache.NewNode(r, singleFlights, stats, sql.ErrNoRows))
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
_, err := c.ExecNoCache("delete from user_table where id='kevin'")
assert.Nil(t, err)
assert.True(t, conn.execValue)
}

View File

@@ -34,9 +34,23 @@ func getTaggedFieldValueMap(v reflect.Value) (map[string]any, error) {
result := make(map[string]any, size)
for i := 0; i < size; i++ {
key := parseTagName(rt.Field(i))
field := rt.Field(i)
if field.Anonymous && mapping.Deref(field.Type).Kind() == reflect.Struct {
inner, err := getTaggedFieldValueMap(reflect.Indirect(v).Field(i))
if err != nil {
return nil, err
}
for key, val := range inner {
result[key] = val
}
continue
}
key := parseTagName(field)
if len(key) == 0 {
return nil, nil
continue
}
valueField := reflect.Indirect(v).Field(i)
@@ -114,7 +128,7 @@ func parseTagName(field reflect.StructField) string {
}
options := strings.Split(key, ",")
return options[0]
return strings.TrimSpace(options[0])
}
func unmarshalRow(v any, scanner rowsScanner, strict bool) error {

View File

@@ -1041,6 +1041,127 @@ func TestUnmarshalRowError(t *testing.T) {
}
}
func TestAnonymousStructPr(t *testing.T) {
type Score struct {
Discipline string `db:"discipline"`
Score uint `db:"score"`
}
type ClassType struct {
Grade sql.NullString `db:"grade"`
ClassName *string `db:"class_name"`
}
type Class struct {
*ClassType
Score
}
expect := []*struct {
Name string
Age int64
Grade sql.NullString
Discipline string
Score uint
ClassName string
}{
{
Name: "first",
Age: 2,
Grade: sql.NullString{
String: "",
Valid: false,
},
ClassName: "experimental class",
Discipline: "math",
Score: 100,
},
{
Name: "second",
Age: 3,
Grade: sql.NullString{
String: "grade one",
Valid: true,
},
ClassName: "class three grade two",
Discipline: "chinese",
Score: 99,
},
}
var value []*struct {
Age int64 `db:"age"`
Class
Name string `db:"name"`
}
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{
"name",
"age",
"grade",
"discipline",
"class_name",
"score",
}).
AddRow("first", 2, nil, "math", "experimental class", 100).
AddRow("second", 3, "grade one", "chinese", "class three grade two", 99)
mock.ExpectQuery("select (.+) from users where user=?").
WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age,grade,discipline,class_name,score from users where user=?",
"anyone"))
for i, each := range expect {
assert.Equal(t, each.Name, value[i].Name)
assert.Equal(t, each.Age, value[i].Age)
assert.Equal(t, each.ClassName, *value[i].Class.ClassName)
assert.Equal(t, each.Discipline, value[i].Score.Discipline)
assert.Equal(t, each.Score, value[i].Score.Score)
assert.Equal(t, each.Grade, value[i].Class.Grade)
}
})
}
func TestAnonymousStructPrError(t *testing.T) {
type Score struct {
Discipline string `db:"discipline"`
score uint `db:"score"`
}
type ClassType struct {
Grade sql.NullString `db:"grade"`
ClassName *string `db:"class_name"`
}
type Class struct {
*ClassType
Score
}
var value []*struct {
Age int64 `db:"age"`
Class
Name string `db:"name"`
}
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{
"name",
"age",
"grade",
"discipline",
"class_name",
"score",
}).
AddRow("first", 2, nil, "math", "experimental class", 100).
AddRow("second", 3, "grade one", "chinese", "class three grade two", 99)
mock.ExpectQuery("select (.+) from users where user=?").
WithArgs("anyone").WillReturnRows(rs)
assert.Error(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age,grade,discipline,class_name,score from users where user=?",
"anyone"))
if len(value) > 0 {
assert.Equal(t, value[0].score, 0)
}
})
}
func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
logx.Disable()

View File

@@ -8,6 +8,7 @@ import (
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/trace/tracetest"
)
const mockedDatasource = "sqlmock"
@@ -17,6 +18,7 @@ func init() {
}
func TestSqlConn(t *testing.T) {
me := tracetest.NewInMemoryExporter(t)
mock, err := buildConn()
assert.Nil(t, err)
mock.ExpectExec("any")
@@ -49,6 +51,7 @@ func TestSqlConn(t *testing.T) {
assert.NotNil(t, badConn.Transact(func(session Session) error {
return nil
}))
assert.Equal(t, 14, len(me.GetSpans()))
}
func buildConn() (mock sqlmock.Sqlmock, err error) {

View File

@@ -159,7 +159,7 @@ func transactOnConn(ctx context.Context, conn *sql.DB, b beginnable,
if e := tx.Rollback(); e != nil {
err = fmt.Errorf("recover from %#v, rollback failed: %w", p, e)
} else {
err = fmt.Errorf("recoveer from %#v", p)
err = fmt.Errorf("recover from %#v", p)
}
} else if err != nil {
if e := tx.Rollback(); e != nil {

View File

@@ -1,6 +1,3 @@
//go:build go1.18
// +build go1.18
package stringx
import (

View File

@@ -0,0 +1,20 @@
package tracetest
import (
"testing"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
)
// NewInMemoryExporter returns a new InMemoryExporter
// and sets it as the global for tests.
func NewInMemoryExporter(t *testing.T) *tracetest.InMemoryExporter {
me := tracetest.NewInMemoryExporter()
t.Cleanup(func() {
me.Reset()
})
otel.SetTracerProvider(trace.NewTracerProvider(trace.WithSyncer(me)))
return me
}

View File

@@ -15,8 +15,10 @@ func TestCompareVersions(t *testing.T) {
out bool
}{
{"1", "1.0.1", ">", false},
{"1.0.1", "1.0", "<", false},
{"1", "0.9.9", ">", true},
{"1", "1.0-1", "<", true},
{"1", "1.0-1", "!", false},
{"1.0.1", "1-0.1", "<", false},
{"1.0.1", "1.0.1", "==", true},
{"1.0.1", "1.0.2", "==", false},
@@ -37,3 +39,21 @@ func TestCompareVersions(t *testing.T) {
})
}
}
func TestStrsToInts(t *testing.T) {
testCases := []struct {
input []string
expected []int64
}{
{[]string{}, nil},
{[]string{"1", "2", "3"}, []int64{1, 2, 3}},
}
for _, tc := range testCases {
tc := tc
t.Run("", func(t *testing.T) {
actual := strsToInts(tc.input)
assert.Equal(t, tc.expected, actual)
})
}
}

View File

@@ -0,0 +1,7 @@
package validation
// Validator represents a validator.
type Validator interface {
// Validate validates the value.
Validate() error
}

View File

@@ -1,8 +1,6 @@
package gateway
import (
"time"
"github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/zrpc"
)
@@ -12,7 +10,6 @@ type (
GatewayConf struct {
rest.RestConf
Upstreams []Upstream
Timeout time.Duration `json:",default=5s"`
}
// RouteMapping is a mapping between a gateway route and an upstream rpc method.

View File

@@ -37,52 +37,51 @@ func GetMethods(source grpcurl.DescriptorSource) ([]Method, error) {
for _, method := range svcMethods {
rpcPath := fmt.Sprintf("%s/%s", svc, method.GetName())
ext := proto.GetExtension(method.GetMethodOptions(), annotations.E_Http)
if ext == nil {
methods = append(methods, Method{
RpcPath: rpcPath,
})
continue
}
switch rule := ext.(type) {
case *annotations.HttpRule:
if rule == nil {
methods = append(methods, Method{
RpcPath: rpcPath,
})
continue
}
httpExt, ok := ext.(*annotations.HttpRule)
if !ok {
methods = append(methods, Method{
RpcPath: rpcPath,
})
continue
}
switch rule := httpExt.GetPattern().(type) {
case *annotations.HttpRule_Get:
methods = append(methods, Method{
HttpMethod: http.MethodGet,
HttpPath: adjustHttpPath(rule.Get),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Post:
methods = append(methods, Method{
HttpMethod: http.MethodPost,
HttpPath: adjustHttpPath(rule.Post),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Put:
methods = append(methods, Method{
HttpMethod: http.MethodPut,
HttpPath: adjustHttpPath(rule.Put),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Delete:
methods = append(methods, Method{
HttpMethod: http.MethodDelete,
HttpPath: adjustHttpPath(rule.Delete),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Patch:
methods = append(methods, Method{
HttpMethod: http.MethodPatch,
HttpPath: adjustHttpPath(rule.Patch),
RpcPath: rpcPath,
})
switch httpRule := rule.GetPattern().(type) {
case *annotations.HttpRule_Get:
methods = append(methods, Method{
HttpMethod: http.MethodGet,
HttpPath: adjustHttpPath(httpRule.Get),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Post:
methods = append(methods, Method{
HttpMethod: http.MethodPost,
HttpPath: adjustHttpPath(httpRule.Post),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Put:
methods = append(methods, Method{
HttpMethod: http.MethodPut,
HttpPath: adjustHttpPath(httpRule.Put),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Delete:
methods = append(methods, Method{
HttpMethod: http.MethodDelete,
HttpPath: adjustHttpPath(httpRule.Delete),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Patch:
methods = append(methods, Method{
HttpMethod: http.MethodPatch,
HttpPath: adjustHttpPath(httpRule.Patch),
RpcPath: rpcPath,
})
default:
methods = append(methods, Method{
RpcPath: rpcPath,
})
}
default:
methods = append(methods, Method{
RpcPath: rpcPath,

View File

@@ -2,11 +2,13 @@ package internal
import (
"encoding/base64"
"errors"
"net/http"
"os"
"testing"
"github.com/fullstorydev/grpcurl"
"github.com/jhump/protoreflect/desc"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/hash"
)
@@ -75,3 +77,50 @@ func TestGetMethodsWithAnnotations(t *testing.T) {
},
}, methods)
}
func TestGetMethodsBadCases(t *testing.T) {
t.Run("no services", func(t *testing.T) {
source := &mockDescriptorSource{
servicesErr: errors.New("no services"),
}
_, err := GetMethods(source)
assert.NotNil(t, err)
})
t.Run("no symbol in services", func(t *testing.T) {
source := &mockDescriptorSource{
services: []string{"hello.Hello"},
symbolErr: errors.New("no symbol"),
}
_, err := GetMethods(source)
assert.NotNil(t, err)
})
t.Run("no symbol in services", func(t *testing.T) {
source := &mockDescriptorSource{
services: []string{"hello.Hello"},
symbolErr: errors.New("no symbol"),
}
_, err := GetMethods(source)
assert.NotNil(t, err)
})
}
type mockDescriptorSource struct {
symbolDesc desc.Descriptor
symbolErr error
services []string
servicesErr error
}
func (m *mockDescriptorSource) AllExtensionsForType(_ string) ([]*desc.FieldDescriptor, error) {
return nil, nil
}
func (m *mockDescriptorSource) FindSymbol(_ string) (desc.Descriptor, error) {
return m.symbolDesc, m.symbolErr
}
func (m *mockDescriptorSource) ListServices() ([]string, error) {
return m.services, m.servicesErr
}

View File

@@ -38,7 +38,6 @@ func MustNewServer(c GatewayConf, opts ...Option) *Server {
svr := &Server{
Server: rest.MustNewServer(c.RestConf),
upstreams: c.Upstreams,
timeout: c.Timeout,
}
for _, opt := range opts {
opt(svr)

34
go.mod
View File

@@ -4,18 +4,18 @@ go 1.18
require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/alicebob/miniredis/v2 v2.30.0
github.com/fatih/color v1.14.1
github.com/alicebob/miniredis/v2 v2.30.1
github.com/fatih/color v1.15.0
github.com/felixge/fgprof v0.9.3
github.com/fullstorydev/grpcurl v1.8.7
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.7.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.2
github.com/golang/protobuf v1.5.3
github.com/google/uuid v1.3.0
github.com/jackc/pgx/v5 v5.3.1
github.com/jhump/protoreflect v1.15.0
github.com/jhump/protoreflect v1.15.1
github.com/olekukonko/tablewriter v0.0.5
github.com/pelletier/go-toml/v2 v2.0.7
github.com/prometheus/client_golang v1.14.0
@@ -23,7 +23,7 @@ require (
github.com/stretchr/testify v1.8.2
go.etcd.io/etcd/api/v3 v3.5.7
go.etcd.io/etcd/client/v3 v3.5.7
go.mongodb.org/mongo-driver v1.11.2
go.mongodb.org/mongo-driver v1.11.4
go.opentelemetry.io/otel v1.14.0
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
@@ -31,26 +31,27 @@ require (
go.opentelemetry.io/otel/exporters/zipkin v1.14.0
go.opentelemetry.io/otel/sdk v1.14.0
go.opentelemetry.io/otel/trace v1.14.0
go.uber.org/automaxprocs v1.5.1
go.uber.org/automaxprocs v1.5.2
go.uber.org/goleak v1.2.1
golang.org/x/sys v0.5.0
golang.org/x/net v0.9.0
golang.org/x/sys v0.7.0
golang.org/x/time v0.3.0
google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8
google.golang.org/grpc v1.54.0
google.golang.org/protobuf v1.30.0
gopkg.in/cheggaaa/pb.v1 v1.0.28
gopkg.in/h2non/gock.v1 v1.1.2
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.26.2
k8s.io/apimachinery v0.26.2
k8s.io/client-go v0.26.2
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
k8s.io/client-go v0.26.3
k8s.io/utils v0.0.0-20230115233650-391b47cb4029
)
require (
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bufbuild/protocompile v0.2.1-0.20230123224550-da57cd758c2f // indirect
github.com/bufbuild/protocompile v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
@@ -96,7 +97,7 @@ require (
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 // indirect
github.com/yuin/gopher-lua v1.1.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
@@ -105,11 +106,10 @@ require (
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.4.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

67
go.sum
View File

@@ -43,16 +43,16 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
github.com/alicebob/miniredis/v2 v2.30.1 h1:HM1rlQjq1bm9yQcsawJqSZBJ9AYgxvjkMsNtddh90+g=
github.com/alicebob/miniredis/v2 v2.30.1/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bufbuild/protocompile v0.2.1-0.20230123224550-da57cd758c2f h1:IXSA5gow10s7zIOJfPOpXDtNBWCTA0715BDAhoJBXEs=
github.com/bufbuild/protocompile v0.2.1-0.20230123224550-da57cd758c2f/go.mod h1:tleDrpPTlLUVmgnEoN6qBliKWqJaZFJXqZdFjTd+ocU=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -95,8 +95,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
@@ -169,8 +169,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -232,8 +233,8 @@ github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+
github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI=
github.com/jhump/protoreflect v1.15.0 h1:U5T5/2LF0AZQFP9T4W5GfBjBaTruomrKobiR4E+oA/Q=
github.com/jhump/protoreflect v1.15.0/go.mod h1:qww51KYjD2hoCl/ohxw5cK2LSssFczrbO1t8Ld2TENs=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -379,16 +380,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
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-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY=
go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg=
go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4=
go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw=
go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw=
go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
go.mongodb.org/mongo-driver v1.11.4 h1:4ayjakA013OdpGyL2K3ZqylTac/rMjrJOMZ1EHizXas=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
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=
@@ -417,8 +418,8 @@ go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJP
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
@@ -499,8 +500,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
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=
@@ -569,12 +570,12 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/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/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
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=
@@ -583,8 +584,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -713,8 +714,8 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
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=
@@ -729,8 +730,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk=
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -765,12 +766,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
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.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ=
k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU=
k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ=
k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI=
k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU=
k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU=
k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE=
k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=
k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s=
k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ=
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=

View File

@@ -23,7 +23,7 @@
>
> `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
>
> `goctl migrate —verbose —version v1.4.3`
> `goctl migrate —verbose —version v1.5.0`
## 0. go-zero 介绍
@@ -119,28 +119,25 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
`goctl` 读作 `go control`,不要读成 `go C-T-L``goctl` 的意思是不要被代码控制,而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初,我就希望通过 `工具` 来解放我们的双手👈
```shell
# Go 1.15 及之前版本
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro/go-zero/tools/goctl@latest
# Go 1.16 及以后版本
# Go
GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
# For Mac
brew install goctl
# docker for amd64 architecture
docker pull kevinwan/goctl
# run goctl like
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
# docker for arm64 (M1) architecture
docker pull kevinwan/goctl:latest-arm64
# run goctl like
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
```
确保 goctl 可执行
2. 快速生成 api 服务
```shell
@@ -301,6 +298,7 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
>86. 中国移动上海产业研究院
>87. 天枢数链(浙江)科技有限公司
>88. 北京娱人共享智能科技有限公司
>89. 北京数智方科技有限公司
如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。

View File

@@ -111,7 +111,7 @@ go install github.com/zeromicro/go-zero/tools/goctl@latest
```
```shell
goctl migrate —verbose —version v1.4.3
goctl migrate —verbose —version v1.5.0
```
## Quick Start
@@ -127,28 +127,25 @@ goctl migrate —verbose —version v1.4.3
`goctl`can be read as `go control`. `goctl` means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve productivity, and make our lives easier.
```shell
# for Go 1.15 and earlier
GO111MODULE=on go get -u github.com/zeromicro/go-zero/tools/goctl@latest
# for Go 1.16 and later
# for Go
go install github.com/zeromicro/go-zero/tools/goctl@latest
# For Mac
brew install goctl
# docker for amd64 architecture
docker pull kevinwan/goctl
# run goctl like
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
# docker for arm64 (M1) architecture
docker pull kevinwan/goctl:latest-arm64
# run goctl like
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
```
make sure goctl is executable.
3. create the API file, like greet.api, you can install the plugin of goctl in vs code, api syntax is supported.
```go

View File

@@ -301,21 +301,29 @@ func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain.Chai
}, nil
}
func (ng *engine) start(router httpx.Router) error {
func (ng *engine) start(router httpx.Router, opts ...StartOption) error {
if err := ng.bindRoutes(router); err != nil {
return err
}
// make sure user defined options overwrite default options
opts = append([]StartOption{ng.withTimeout()}, opts...)
if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 {
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router, ng.withTimeout())
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router, opts...)
}
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
ng.conf.KeyFile, router, func(svr *http.Server) {
// make sure user defined options overwrite default options
opts = append([]StartOption{
func(svr *http.Server) {
if ng.tlsConfig != nil {
svr.TLSConfig = ng.tlsConfig
}
}, ng.withTimeout())
},
}, opts...)
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
ng.conf.KeyFile, router, opts...)
}
func (ng *engine) use(middleware Middleware) {

View File

@@ -3,6 +3,7 @@ package rest
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"sync/atomic"
@@ -17,18 +18,21 @@ import (
func TestNewEngine(t *testing.T) {
yamls := []string{
`Name: foo
Port: 54321
Host: localhost
Port: 0
Middlewares:
Log: false
`,
`Name: foo
Port: 54321
Host: localhost
Port: 0
CpuThreshold: 500
Middlewares:
Log: false
`,
`Name: foo
Port: 54321
Host: localhost
Port: 0
CpuThreshold: 500
Verbose: true
`,
@@ -150,22 +154,29 @@ Verbose: true
}
for _, yaml := range yamls {
yaml := yaml
for _, route := range routes {
var cnf RestConf
assert.Nil(t, conf.LoadFromYamlBytes([]byte(yaml), &cnf))
ng := newEngine(cnf)
ng.addRoutes(route)
ng.use(func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
route := route
t.Run(fmt.Sprintf("%s-%v", yaml, route.routes), func(t *testing.T) {
var cnf RestConf
assert.Nil(t, conf.LoadFromYamlBytes([]byte(yaml), &cnf))
ng := newEngine(cnf)
ng.addRoutes(route)
ng.use(func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
}
})
assert.NotNil(t, ng.start(mockedRouter{}, func(svr *http.Server) {
}))
timeout := time.Second * 3
if route.timeout > timeout {
timeout = route.timeout
}
assert.Equal(t, timeout, ng.timeout)
})
assert.NotNil(t, ng.start(mockedRouter{}))
timeout := time.Second * 3
if route.timeout > timeout {
timeout = route.timeout
}
assert.Equal(t, timeout, ng.timeout)
}
}
}
@@ -340,7 +351,8 @@ func TestEngine_withTimeout(t *testing.T) {
}
}
type mockedRouter struct{}
type mockedRouter struct {
}
func (m mockedRouter) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
}

View File

@@ -379,8 +379,7 @@ func createTempFile(body []byte) (string, error) {
}
tmpFile.Close()
err = os.WriteFile(tmpFile.Name(), body, os.ModePerm)
if err != nil {
if err = os.WriteFile(tmpFile.Name(), body, os.ModePerm); err != nil {
return "", err
}

View File

@@ -183,7 +183,10 @@ func (tw *timeoutWriter) writeHeaderLocked(code int) {
func (tw *timeoutWriter) WriteHeader(code int) {
tw.mu.Lock()
defer tw.mu.Unlock()
tw.writeHeaderLocked(code)
if !tw.wroteHeader {
tw.writeHeaderLocked(code)
}
}
func checkWriteHeaderCode(code int) {

View File

@@ -10,9 +10,12 @@ import (
"github.com/stretchr/testify/assert"
ztrace "github.com/zeromicro/go-zero/core/trace"
"github.com/zeromicro/go-zero/core/trace/tracetest"
"github.com/zeromicro/go-zero/rest/chain"
"go.opentelemetry.io/otel"
tcodes "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
@@ -54,6 +57,31 @@ func TestOtelHandler(t *testing.T) {
}
}
func TestTraceHandler(t *testing.T) {
me := tracetest.NewInMemoryExporter(t)
h := chain.New(TraceHandler("foo", "/")).Then(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts := httptest.NewServer(h)
defer ts.Close()
client := ts.Client()
err := func(ctx context.Context) error {
req, _ := http.NewRequest("GET", ts.URL, nil)
res, err := client.Do(req)
assert.Nil(t, err)
return res.Body.Close()
}(context.Background())
assert.NoError(t, err)
assert.Equal(t, 1, len(me.GetSpans()))
span := me.GetSpans()[0].Snapshot()
assert.Equal(t, sdktrace.Status{
Code: tcodes.Unset,
}, span.Status())
assert.Equal(t, 0, len(span.Events()))
assert.Equal(t, 9, len(span.Attributes()))
}
func TestDontTracingSpan(t *testing.T) {
ztrace.StartAgent(ztrace.Config{
Name: "go-zero-test",

View File

@@ -10,9 +10,12 @@ import (
"github.com/stretchr/testify/assert"
ztrace "github.com/zeromicro/go-zero/core/trace"
"github.com/zeromicro/go-zero/core/trace/tracetest"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/internal/header"
"github.com/zeromicro/go-zero/rest/router"
tcodes "go.opentelemetry.io/otel/codes"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
@@ -59,6 +62,7 @@ func TestDoRequest_Moved(t *testing.T) {
}
func TestDo(t *testing.T) {
me := tracetest.NewInMemoryExporter(t)
type Data struct {
Key string `path:"key"`
Value int `form:"value"`
@@ -86,6 +90,13 @@ func TestDo(t *testing.T) {
resp, err := Do(context.Background(), http.MethodPost, svr.URL+"/nodes/:key", data)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, 1, len(me.GetSpans()))
span := me.GetSpans()[0].Snapshot()
assert.Equal(t, sdktrace.Status{
Code: tcodes.Unset,
}, span.Status())
assert.Equal(t, 0, len(span.Events()))
assert.Equal(t, 7, len(span.Attributes()))
}
func TestDo_Ptr(t *testing.T) {

View File

@@ -7,6 +7,7 @@ import (
"sync/atomic"
"github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/core/validation"
"github.com/zeromicro/go-zero/rest/internal/encoding"
"github.com/zeromicro/go-zero/rest/internal/header"
"github.com/zeromicro/go-zero/rest/pathvar"
@@ -51,7 +52,9 @@ func Parse(r *http.Request, v any) error {
return err
}
if val := validator.Load(); val != nil {
if valid, ok := v.(validation.Validator); ok {
return valid.Validate()
} else if val := validator.Load(); val != nil {
return val.(Validator).Validate(r, v)
}

View File

@@ -354,6 +354,14 @@ func TestParseWithValidatorWithError(t *testing.T) {
assert.Error(t, Parse(r, &v))
}
func TestParseWithValidatorRequest(t *testing.T) {
SetValidator(mockValidator{})
var v mockRequest
r, err := http.NewRequest(http.MethodGet, "/a?&age=18", http.NoBody)
assert.Nil(t, err)
assert.Error(t, Parse(r, &v))
}
func BenchmarkParseRaw(b *testing.B) {
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody)
if err != nil {
@@ -410,3 +418,15 @@ func (m mockValidator) Validate(r *http.Request, data any) error {
return nil
}
type mockRequest struct {
Name string `json:"name,optional"`
}
func (m mockRequest) Validate() error {
if m.Name != "hello" {
return errors.New("name is not hello")
}
return nil
}

View File

@@ -77,12 +77,19 @@ func checkAndSetHeaders(w http.ResponseWriter, r *http.Request, origins []string
}
func isOriginAllowed(allows []string, origin string) bool {
for _, o := range allows {
if o == allOrigins {
origin = strings.ToLower(origin)
for _, allow := range allows {
if allow == allOrigins {
return true
}
if strings.HasSuffix(origin, o) {
allow = strings.ToLower(allow)
if origin == allow {
return true
}
if strings.HasSuffix(origin, "."+allow) {
return true
}
}

View File

@@ -53,6 +53,11 @@ func TestCorsHandlerWithOrigins(t *testing.T) {
origins: []string{"http://local", "http://remote"},
reqOrigin: "http://another",
},
{
name: "not safe origin",
origins: []string{"safe.com"},
reqOrigin: "not-safe.com",
},
}
methods := []string{

View File

@@ -2,7 +2,6 @@ package rest
import (
"crypto/tls"
"log"
"net/http"
"path"
"time"
@@ -11,6 +10,7 @@ import (
"github.com/zeromicro/go-zero/rest/chain"
"github.com/zeromicro/go-zero/rest/handler"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/internal"
"github.com/zeromicro/go-zero/rest/internal/cors"
"github.com/zeromicro/go-zero/rest/router"
)
@@ -19,6 +19,9 @@ type (
// RunOption defines the method to customize a Server.
RunOption func(*Server)
// StartOption defines the method to customize http server.
StartOption = internal.StartOption
// A Server is a http server.
Server struct {
ngin *engine
@@ -32,7 +35,7 @@ type (
func MustNewServer(c RestConf, opts ...RunOption) *Server {
server, err := NewServer(c, opts...)
if err != nil {
log.Fatal(err)
logx.Must(err)
}
return server
@@ -116,6 +119,13 @@ func (s *Server) Start() {
handleError(s.ngin.start(s.router))
}
// StartWithOpts starts the Server.
// Graceful shutdown is enabled by default.
// Use proc.SetTimeToForceQuit to customize the graceful shutdown period.
func (s *Server) StartWithOpts(opts ...StartOption) {
handleError(s.ngin.start(s.router, opts...))
}
// Stop stops the Server.
func (s *Server) Stop() {
logx.Close()

View File

@@ -28,7 +28,8 @@ func TestNewServer(t *testing.T) {
const configYaml = `
Name: foo
Port: 54321
Host: localhost
Port: 0
`
var cnf RestConf
assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
@@ -101,6 +102,23 @@ Port: 54321
svr.Start()
svr.Stop()
}()
func() {
defer func() {
p := recover()
switch v := p.(type) {
case error:
assert.Equal(t, "foo", v.Error())
default:
t.Fail()
}
}()
svr.StartWithOpts(func(svr *http.Server) {
svr.RegisterOnShutdown(func() {})
})
svr.Stop()
}()
}
}
@@ -569,7 +587,6 @@ Port: 54321
Method: http.MethodGet,
Path: "/user/:name",
Handler: func(writer http.ResponseWriter, request *http.Request) {
var userInfo struct {
Name string `path:"name"`
}

View File

@@ -13,165 +13,91 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/api/tsgen"
"github.com/zeromicro/go-zero/tools/goctl/api/validate"
"github.com/zeromicro/go-zero/tools/goctl/config"
"github.com/zeromicro/go-zero/tools/goctl/internal/cobrax"
"github.com/zeromicro/go-zero/tools/goctl/plugin"
)
var (
// Cmd describes an api command.
Cmd = &cobra.Command{
Use: "api",
Short: "Generate api related files",
RunE: apigen.CreateApiTemplate,
}
dartCmd = &cobra.Command{
Use: "dart",
Short: "Generate dart files for provided api in api file",
RunE: dartgen.DartCommand,
}
docCmd = &cobra.Command{
Use: "doc",
Short: "Generate doc files",
RunE: docgen.DocCommand,
}
formatCmd = &cobra.Command{
Use: "format",
Short: "Format api files",
RunE: format.GoFormatApi,
}
goCmd = &cobra.Command{
Use: "go",
Short: "Generate go files for provided api in api file",
RunE: gogen.GoCommand,
}
newCmd = &cobra.Command{
Use: "new",
Short: "Fast create api service",
Example: "goctl api new [options] service-name",
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
RunE: func(cmd *cobra.Command, args []string) error {
return new.CreateServiceCommand(args)
},
}
validateCmd = &cobra.Command{
Use: "validate",
Short: "Validate api file",
RunE: validate.GoValidateApi,
}
javaCmd = &cobra.Command{
Use: "java",
Short: "Generate java files for provided api in api file",
Hidden: true,
RunE: javagen.JavaCommand,
}
ktCmd = &cobra.Command{
Use: "kt",
Short: "Generate kotlin code for provided api file",
RunE: ktgen.KtCommand,
}
pluginCmd = &cobra.Command{
Use: "plugin",
Short: "Custom file generator",
RunE: plugin.PluginCommand,
}
tsCmd = &cobra.Command{
Use: "ts",
Short: "Generate ts files for provided api in api file",
RunE: tsgen.TsCommand,
}
Cmd = cobrax.NewCommand("api", cobrax.WithRunE(apigen.CreateApiTemplate))
dartCmd = cobrax.NewCommand("dart", cobrax.WithRunE(dartgen.DartCommand))
docCmd = cobrax.NewCommand("doc", cobrax.WithRunE(docgen.DocCommand))
formatCmd = cobrax.NewCommand("format", cobrax.WithRunE(format.GoFormatApi))
goCmd = cobrax.NewCommand("go", cobrax.WithRunE(gogen.GoCommand))
newCmd = cobrax.NewCommand("new", cobrax.WithRunE(new.CreateServiceCommand),
cobrax.WithArgs(cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs)))
validateCmd = cobrax.NewCommand("validate", cobrax.WithRunE(validate.GoValidateApi))
javaCmd = cobrax.NewCommand("java", cobrax.WithRunE(javagen.JavaCommand), cobrax.WithHidden())
ktCmd = cobrax.NewCommand("kt", cobrax.WithRunE(ktgen.KtCommand))
pluginCmd = cobrax.NewCommand("plugin", cobrax.WithRunE(plugin.PluginCommand))
tsCmd = cobrax.NewCommand("ts", cobrax.WithRunE(tsgen.TsCommand))
)
func init() {
Cmd.Flags().StringVar(&apigen.VarStringOutput, "o", "", "Output a sample api file")
Cmd.Flags().StringVar(&apigen.VarStringHome, "home", "", "The goctl home path of the"+
" template, --home and --remote cannot be set at the same time, if they are, --remote has "+
"higher priority")
Cmd.Flags().StringVar(&apigen.VarStringRemote, "remote", "", "The remote git repo of the"+
" template, --home and --remote cannot be set at the same time, if they are, --remote has higher"+
" priority\nThe git repo directory must be consistent with the"+
" https://github.com/zeromicro/go-zero-template directory structure")
Cmd.Flags().StringVar(&apigen.VarStringBranch, "branch", "", "The branch of the "+
"remote repo, it does work with --remote")
var (
apiCmdFlags = Cmd.Flags()
dartCmdFlags = dartCmd.Flags()
docCmdFlags = docCmd.Flags()
formatCmdFlags = formatCmd.Flags()
goCmdFlags = goCmd.Flags()
javaCmdFlags = javaCmd.Flags()
ktCmdFlags = ktCmd.Flags()
newCmdFlags = newCmd.Flags()
pluginCmdFlags = pluginCmd.Flags()
tsCmdFlags = tsCmd.Flags()
validateCmdFlags = validateCmd.Flags()
)
dartCmd.Flags().StringVar(&dartgen.VarStringDir, "dir", "", "The target dir")
dartCmd.Flags().StringVar(&dartgen.VarStringAPI, "api", "", "The api file")
dartCmd.Flags().BoolVar(&dartgen.VarStringLegacy, "legacy", false, "Legacy generator for flutter v1")
dartCmd.Flags().StringVar(&dartgen.VarStringHostname, "hostname", "", "hostname of the server")
apiCmdFlags.StringVar(&apigen.VarStringOutput, "o")
apiCmdFlags.StringVar(&apigen.VarStringHome, "home")
apiCmdFlags.StringVar(&apigen.VarStringRemote, "remote")
apiCmdFlags.StringVar(&apigen.VarStringBranch, "branch")
docCmd.Flags().StringVar(&docgen.VarStringDir, "dir", "", "The target dir")
docCmd.Flags().StringVar(&docgen.VarStringOutput, "o", "", "The output markdown directory")
dartCmdFlags.StringVar(&dartgen.VarStringDir, "dir")
dartCmdFlags.StringVar(&dartgen.VarStringAPI, "api")
dartCmdFlags.BoolVar(&dartgen.VarStringLegacy, "legacy")
dartCmdFlags.StringVar(&dartgen.VarStringHostname, "hostname")
dartCmdFlags.StringVar(&dartgen.VarStringScheme, "scheme")
formatCmd.Flags().StringVar(&format.VarStringDir, "dir", "", "The format target dir")
formatCmd.Flags().BoolVar(&format.VarBoolIgnore, "iu", false, "Ignore update")
formatCmd.Flags().BoolVar(&format.VarBoolUseStdin, "stdin", false, "Use stdin to input api"+
" doc content, press \"ctrl + d\" to send EOF")
formatCmd.Flags().BoolVar(&format.VarBoolSkipCheckDeclare, "declare", false, "Use to skip check "+
"api types already declare")
docCmdFlags.StringVar(&docgen.VarStringDir, "dir")
docCmdFlags.StringVar(&docgen.VarStringOutput, "o")
goCmd.Flags().StringVar(&gogen.VarStringDir, "dir", "", "The target dir")
goCmd.Flags().StringVar(&gogen.VarStringAPI, "api", "", "The api file")
goCmd.Flags().StringVar(&gogen.VarStringHome, "home", "", "The goctl home path of "+
"the template, --home and --remote cannot be set at the same time, if they are, --remote "+
"has higher priority")
goCmd.Flags().StringVar(&gogen.VarStringRemote, "remote", "", "The remote git repo "+
"of the template, --home and --remote cannot be set at the same time, if they are, --remote"+
" has higher priority\nThe git repo directory must be consistent with the "+
"https://github.com/zeromicro/go-zero-template directory structure")
goCmd.Flags().StringVar(&gogen.VarStringBranch, "branch", "", "The branch of "+
"the remote repo, it does work with --remote")
goCmd.Flags().StringVar(&gogen.VarStringStyle, "style", config.DefaultFormat, "The file naming format,"+
" see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md]")
formatCmdFlags.StringVar(&format.VarStringDir, "dir")
formatCmdFlags.BoolVar(&format.VarBoolIgnore, "iu")
formatCmdFlags.BoolVar(&format.VarBoolUseStdin, "stdin")
formatCmdFlags.BoolVar(&format.VarBoolSkipCheckDeclare, "declare")
javaCmd.Flags().StringVar(&javagen.VarStringDir, "dir", "", "The target dir")
javaCmd.Flags().StringVar(&javagen.VarStringAPI, "api", "", "The api file")
goCmdFlags.StringVar(&gogen.VarStringDir, "dir")
goCmdFlags.StringVar(&gogen.VarStringAPI, "api")
goCmdFlags.StringVar(&gogen.VarStringHome, "home")
goCmdFlags.StringVar(&gogen.VarStringRemote, "remote")
goCmdFlags.StringVar(&gogen.VarStringBranch, "branch")
goCmdFlags.StringVarWithDefaultValue(&gogen.VarStringStyle, "style", config.DefaultFormat)
ktCmd.Flags().StringVar(&ktgen.VarStringDir, "dir", "", "The target dir")
ktCmd.Flags().StringVar(&ktgen.VarStringAPI, "api", "", "The api file")
ktCmd.Flags().StringVar(&ktgen.VarStringPKG, "pkg", "", "Define package name for kotlin file")
javaCmdFlags.StringVar(&javagen.VarStringDir, "dir")
javaCmdFlags.StringVar(&javagen.VarStringAPI, "api")
newCmd.Flags().StringVar(&new.VarStringHome, "home", "", "The goctl home path of "+
"the template, --home and --remote cannot be set at the same time, if they are, --remote "+
"has higher priority")
newCmd.Flags().StringVar(&new.VarStringRemote, "remote", "", "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")
newCmd.Flags().StringVar(&new.VarStringBranch, "branch", "", "The branch of "+
"the remote repo, it does work with --remote")
newCmd.Flags().StringVar(&new.VarStringStyle, "style", config.DefaultFormat, "The file naming format,"+
" see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md]")
ktCmdFlags.StringVar(&ktgen.VarStringDir, "dir")
ktCmdFlags.StringVar(&ktgen.VarStringAPI, "api")
ktCmdFlags.StringVar(&ktgen.VarStringPKG, "pkg")
pluginCmd.Flags().StringVarP(&plugin.VarStringPlugin, "plugin", "p", "", "The plugin file")
pluginCmd.Flags().StringVar(&plugin.VarStringDir, "dir", "", "The target dir")
pluginCmd.Flags().StringVar(&plugin.VarStringAPI, "api", "", "The api file")
pluginCmd.Flags().StringVar(&plugin.VarStringStyle, "style", "",
"The file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]")
newCmdFlags.StringVar(&new.VarStringHome, "home")
newCmdFlags.StringVar(&new.VarStringRemote, "remote")
newCmdFlags.StringVar(&new.VarStringBranch, "branch")
newCmdFlags.StringVarWithDefaultValue(&new.VarStringStyle, "style", config.DefaultFormat)
tsCmd.Flags().StringVar(&tsgen.VarStringDir, "dir", "", "The target dir")
tsCmd.Flags().StringVar(&tsgen.VarStringAPI, "api", "", "The api file")
tsCmd.Flags().StringVar(&tsgen.VarStringCaller, "caller", "", "The web api caller")
tsCmd.Flags().BoolVar(&tsgen.VarBoolUnWrap, "unwrap", false, "Unwrap the webapi caller for import")
pluginCmdFlags.StringVarP(&plugin.VarStringPlugin, "plugin", "p")
pluginCmdFlags.StringVar(&plugin.VarStringDir, "dir")
pluginCmdFlags.StringVar(&plugin.VarStringAPI, "api")
pluginCmdFlags.StringVar(&plugin.VarStringStyle, "style")
validateCmd.Flags().StringVar(&validate.VarStringAPI, "api", "", "Validate target api file")
tsCmdFlags.StringVar(&tsgen.VarStringDir, "dir")
tsCmdFlags.StringVar(&tsgen.VarStringAPI, "api")
tsCmdFlags.StringVar(&tsgen.VarStringCaller, "caller")
tsCmdFlags.BoolVar(&tsgen.VarBoolUnWrap, "unwrap")
validateCmdFlags.StringVar(&validate.VarStringAPI, "api")
// Add sub-commands
Cmd.AddCommand(dartCmd)
Cmd.AddCommand(docCmd)
Cmd.AddCommand(formatCmd)
Cmd.AddCommand(goCmd)
Cmd.AddCommand(javaCmd)
Cmd.AddCommand(ktCmd)
Cmd.AddCommand(newCmd)
Cmd.AddCommand(pluginCmd)
Cmd.AddCommand(tsCmd)
Cmd.AddCommand(validateCmd)
Cmd.AddCommand(dartCmd, docCmd, formatCmd, goCmd, javaCmd, ktCmd, newCmd, pluginCmd, tsCmd, validateCmd)
}

View File

@@ -19,6 +19,8 @@ var (
VarStringLegacy bool
// VarStringHostname defines the hostname.
VarStringHostname string
// VarStringSchema defines the scheme.
VarStringScheme string
)
// DartCommand create dart network request code
@@ -27,6 +29,7 @@ func DartCommand(_ *cobra.Command, _ []string) error {
dir := VarStringDir
isLegacy := VarStringLegacy
hostname := VarStringHostname
scheme := VarStringScheme
if len(apiFile) == 0 {
return errors.New("missing -api")
}
@@ -37,6 +40,10 @@ func DartCommand(_ *cobra.Command, _ []string) error {
fmt.Println("you could use '-hostname' flag to specify your server hostname")
hostname = "go-zero.dev"
}
if len(scheme) == 0 {
fmt.Println("you could use '-scheme' flag to specify your server scheme")
scheme = "http"
}
api, err := parser.Parse(apiFile)
if err != nil {
@@ -54,7 +61,7 @@ func DartCommand(_ *cobra.Command, _ []string) error {
api.Info.Title = strings.Replace(apiFile, ".api", "", -1)
logx.Must(genData(dir+"data/", api, isLegacy))
logx.Must(genApi(dir+"api/", api, isLegacy))
logx.Must(genVars(dir+"vars/", isLegacy, hostname))
logx.Must(genVars(dir+"vars/", isLegacy, scheme, hostname))
if err := formatDir(dir); err != nil {
logx.Errorf("failed to format, %v", err)
}

View File

@@ -13,7 +13,7 @@ const dataTemplate = `// --{{with .Info}}{{.Title}}{{end}}--
class {{.Name}}{
{{range .Members}}
/// {{.Comment}}
final {{.Type.Name}} {{lowCamelCase .Name}};
final {{if isNumberType .Type.Name}}num{{else}}{{.Type.Name}}{{end}} {{lowCamelCase .Name}};
{{end}}
{{.Name}}({ {{range .Members}}
this.{{lowCamelCase .Name}},{{end}}
@@ -37,7 +37,7 @@ const dataTemplateV2 = `// --{{with .Info}}{{.Title}}{{end}}--
class {{.Name}} {
{{range .Members}}
{{if .Comment}}{{.Comment}}{{end}}
final {{.Type.Name}} {{lowCamelCase .Name}};
final {{if isNumberType .Type.Name}}num{{else}}{{.Type.Name}}{{end}} {{lowCamelCase .Name}};
{{end}}{{.Name}}({{if .Members}}{
{{range .Members}} required this.{{lowCamelCase .Name}},
{{end}}}{{end}});

View File

@@ -76,14 +76,14 @@ Future<Tokens?> getTokens() async {
}`
)
func genVars(dir string, isLegacy bool, hostname string) error {
func genVars(dir string, isLegacy bool, scheme string, 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(fmt.Sprintf(`const serverHost='%s';`, hostname)), 0o644)
err = ioutil.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s://%s';`, scheme, hostname)), 0o644)
if err != nil {
return err
}

View File

@@ -57,6 +57,15 @@ func isAtomicType(s string) bool {
}
}
func isNumberType(s string) bool {
switch s {
case "int", "double":
return true
default:
return false
}
}
func isListType(s string) bool {
return strings.HasPrefix(s, "List<")
}

View File

@@ -6,6 +6,7 @@ var funcMap = template.FuncMap{
"getBaseName": getBaseName,
"getPropertyFromMember": getPropertyFromMember,
"isDirectType": isDirectType,
"isNumberType": isNumberType,
"isClassListType": isClassListType,
"getCoreType": getCoreType,
"lowCamelCase": lowCamelCase,
@@ -56,12 +57,21 @@ Future _apiRequest(String method, String path, dynamic data,
var client = HttpClient();
HttpClientRequest r;
if (method == 'POST') {
r = await client.postUrl(Uri.parse('https://' + serverHost + path));
r = await client.postUrl(Uri.parse(serverHost + path));
} else {
r = await client.getUrl(Uri.parse('https://' + serverHost + path));
r = await client.getUrl(Uri.parse(serverHost + path));
}
var strData = '';
if (data != null) {
strData = jsonEncode(data);
}
if (method == 'POST') {
r.headers.set('Content-Type', 'application/json; charset=utf-8');
r.headers.set('Content-Length', utf8.encode(strData).length);
}
r.headers.set('Content-Type', 'application/json; charset=utf-8');
if (tokens != null) {
r.headers.set('Authorization', tokens.accessToken);
}
@@ -70,11 +80,9 @@ Future _apiRequest(String method, String path, dynamic data,
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');
@@ -147,12 +155,19 @@ Future _apiRequest(String method, String path, dynamic data,
var client = HttpClient();
HttpClientRequest r;
if (method == 'POST') {
r = await client.postUrl(Uri.parse('https://' + serverHost + path));
r = await client.postUrl(Uri.parse(serverHost + path));
} else {
r = await client.getUrl(Uri.parse('https://' + serverHost + path));
r = await client.getUrl(Uri.parse(serverHost + path));
}
r.headers.set('Content-Type', 'application/json; charset=utf-8');
var strData = '';
if (data != null) {
strData = jsonEncode(data);
}
if (method == 'POST') {
r.headers.set('Content-Type', 'application/json; charset=utf-8');
r.headers.set('Content-Length', utf8.encode(strData).length);
}
if (tokens != null) {
r.headers.set('Authorization', tokens.accessToken);
}
@@ -161,10 +176,7 @@ Future _apiRequest(String method, String path, dynamic data,
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();

View File

@@ -15,6 +15,8 @@ import (
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/tools/goctl/api/parser"
"github.com/zeromicro/go-zero/tools/goctl/api/util"
"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
apiF "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/format"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
@@ -90,6 +92,10 @@ func apiFormatReader(reader io.Reader, filename string, skipCheckDeclare bool) e
// ApiFormatByPath format api from file path
func ApiFormatByPath(apiFilePath string, skipCheckDeclare bool) error {
if env.UseExperimental() {
return apiF.File(apiFilePath)
}
data, err := os.ReadFile(apiFilePath)
if err != nil {
return err

View File

@@ -158,7 +158,7 @@ func sweep() error {
tm := time.Unix(seconds, 0)
if tm.Before(keepTime) {
if err := os.Remove(fpath); err != nil {
if err := os.RemoveAll(fpath); err != nil {
fmt.Println(aurora.Red(fmt.Sprintf("failed to remove file: %s", fpath)))
return err
}

View File

@@ -1,4 +1,4 @@
package {{with .Info}}{{.Desc}}{{end}}
package {{.Pkg}}
import com.google.gson.Gson

View File

@@ -82,5 +82,9 @@ func genApi(dir, pkg string, api *spec.ApiSpec) error {
if e != nil {
return e
}
return t.Execute(file, api)
type data struct {
*spec.ApiSpec
Pkg string
}
return t.Execute(file, data{api, pkg})
}

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