Compare commits
81 Commits
tools/goct
...
v1.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22fad4bb9c | ||
|
|
189e9bd9da | ||
|
|
98c9b5928a | ||
|
|
e13fd62d38 | ||
|
|
ffacae89eb | ||
|
|
49135fe25e | ||
|
|
2e6402f4b5 | ||
|
|
07f03ebd0c | ||
|
|
92f2676afc | ||
|
|
1807305e6d | ||
|
|
38a97d4531 | ||
|
|
b9f98ecc4a | ||
|
|
1dc222f4b2 | ||
|
|
a79b8de24d | ||
|
|
5da8a93c75 | ||
|
|
b49fc81618 | ||
|
|
6a692453dc | ||
|
|
8d0cceb80c | ||
|
|
e06abf4f6f | ||
|
|
ee555a85da | ||
|
|
1904af2323 | ||
|
|
95b85336d6 | ||
|
|
ca4ce7bce8 | ||
|
|
9065eb90d9 | ||
|
|
50bc361430 | ||
|
|
455a6c8f97 | ||
|
|
04434646eb | ||
|
|
992a56e90b | ||
|
|
ed4d5e5813 | ||
|
|
fe85e7cb42 | ||
|
|
9c6b516bb8 | ||
|
|
2e9063a9a1 | ||
|
|
c3648be533 | ||
|
|
0ab06f62ca | ||
|
|
6170d7b790 | ||
|
|
18d163c4f7 | ||
|
|
a561048d59 | ||
|
|
7a647ca40c | ||
|
|
3f6f14f976 | ||
|
|
a78d57bebd | ||
|
|
74452eb7b5 | ||
|
|
a9e364a01a | ||
|
|
29c2e20b41 | ||
|
|
42c146bcbd | ||
|
|
b61e364458 | ||
|
|
18a4dcb79f | ||
|
|
60a13f1e53 | ||
|
|
3e093bf34e | ||
|
|
211b9498ef | ||
|
|
cca45be3c5 | ||
|
|
e735915d89 | ||
|
|
f77e2c9cfa | ||
|
|
544aa7c432 | ||
|
|
4cef2b412c | ||
|
|
123c61ad12 | ||
|
|
fbf129d535 | ||
|
|
c8a17a97be | ||
|
|
3a493cd6a6 | ||
|
|
7a0c04bc21 | ||
|
|
3c9fe0b381 | ||
|
|
f8b2dc8c9f | ||
|
|
37cb00d789 | ||
|
|
e3e7bc736b | ||
|
|
fafbee24b8 | ||
|
|
8ec29d29ce | ||
|
|
cb7f3e8a17 | ||
|
|
03391b48ca | ||
|
|
d0dedb0624 | ||
|
|
e136deb3a7 | ||
|
|
a2592a17e9 | ||
|
|
05abf4a2ff | ||
|
|
d40000d4b9 | ||
|
|
4620924105 | ||
|
|
a05fe7bf0a | ||
|
|
dd347e96b0 | ||
|
|
a972f400c6 | ||
|
|
fb7664a764 | ||
|
|
7d5d7d9085 | ||
|
|
9911c11e9c | ||
|
|
0d5a68869d | ||
|
|
d9d79e930d |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -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
|
||||
|
||||
13
.github/workflows/go.yml
vendored
13
.github/workflows/go.yml
vendored
@@ -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 ./...
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package fs
|
||||
|
||||
|
||||
@@ -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()),
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]"`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -144,6 +144,10 @@ func TestParseSegments(t *testing.T) {
|
||||
input: "",
|
||||
expect: []string{},
|
||||
},
|
||||
{
|
||||
input: " ",
|
||||
expect: []string{},
|
||||
},
|
||||
{
|
||||
input: ",",
|
||||
expect: []string{""},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build fuzz
|
||||
// +build fuzz
|
||||
|
||||
package mr
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package proc
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package proc
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package proc
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package proc
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package proc
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package proc
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package proc
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package proc
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package proc
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build debug
|
||||
// +build debug
|
||||
|
||||
package search
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package stat
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package stat
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package stat
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package internal
|
||||
|
||||
|
||||
@@ -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
64
core/stat/usage_test.go
Normal 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)
|
||||
}
|
||||
12
core/stores/cache/cache_test.go
vendored
12
core/stores/cache/cache_test.go
vendored
@@ -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{
|
||||
|
||||
44
core/stores/cache/cachenode_test.go
vendored
44
core/stores/cache/cachenode_test.go
vendored
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
43
core/stores/redis/redisclustermanager_test.go
Normal file
43
core/stores/redis/redisclustermanager_test.go
Normal 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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package stringx
|
||||
|
||||
import (
|
||||
|
||||
20
core/trace/tracetest/tracetest.go
Normal file
20
core/trace/tracetest/tracetest.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
7
core/validation/validator.go
Normal file
7
core/validation/validator.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package validation
|
||||
|
||||
// Validator represents a validator.
|
||||
type Validator interface {
|
||||
// Validate validates the value.
|
||||
Validate() error
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
34
go.mod
@@ -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
67
go.sum
@@ -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=
|
||||
|
||||
18
readme-cn.md
18
readme-cn.md
@@ -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) 登记,仅仅为了推广,不做其它用途。
|
||||
|
||||
|
||||
17
readme.md
17
readme.md
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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}});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<")
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package {{with .Info}}{{.Desc}}{{end}}
|
||||
package {{.Pkg}}
|
||||
|
||||
import com.google.gson.Gson
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user