mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-21 21:58:18 +08:00
Compare commits
8 Commits
copilot/fi
...
v1.9.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75941aedd4 | ||
|
|
c7065171d7 | ||
|
|
052de3b552 | ||
|
|
866613af8c | ||
|
|
3d4f6a5e16 | ||
|
|
d1d47d02d5 | ||
|
|
d6c876860b | ||
|
|
98423ca948 |
@@ -40,7 +40,7 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// New create a Filter, store is the backed redis, key is the key for the bloom filter,
|
// New creates a Filter, store is the backed redis, key is the key for the bloom filter,
|
||||||
// bits is how many bits will be used, maps is how many hashes for each addition.
|
// bits is how many bits will be used, maps is how many hashes for each addition.
|
||||||
// best practices:
|
// best practices:
|
||||||
// elements - means how many actual elements
|
// elements - means how many actual elements
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ func (c *Cache) Del(key string) {
|
|||||||
delete(c.data, key)
|
delete(c.data, key)
|
||||||
c.lruCache.remove(key)
|
c.lruCache.remove(key)
|
||||||
c.lock.Unlock()
|
c.lock.Unlock()
|
||||||
|
|
||||||
|
// RemoveTimer is called outside the lock to avoid performance impact from this
|
||||||
|
// potentially time-consuming operation. Data integrity is maintained by lruCache,
|
||||||
|
// which will eventually evict any remaining entries when capacity is exceeded.
|
||||||
c.timingWheel.RemoveTimer(key)
|
c.timingWheel.RemoveTimer(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ func (s Stream) Count() (count int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Distinct removes the duplicated items base on the given KeyFunc.
|
// Distinct removes the duplicated items based on the given KeyFunc.
|
||||||
func (s Stream) Distinct(fn KeyFunc) Stream {
|
func (s Stream) Distinct(fn KeyFunc) Stream {
|
||||||
source := make(chan any)
|
source := make(chan any)
|
||||||
|
|
||||||
@@ -459,7 +459,7 @@ func (s Stream) Tail(n int64) Stream {
|
|||||||
return Range(source)
|
return Range(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk lets the callers handle each item, the caller may write zero, one or more items base on the given item.
|
// Walk lets the callers handle each item, the caller may write zero, one or more items based on the given item.
|
||||||
func (s Stream) Walk(fn WalkFunc, opts ...Option) Stream {
|
func (s Stream) Walk(fn WalkFunc, opts ...Option) Stream {
|
||||||
option := buildOptions(opts...)
|
option := buildOptions(opts...)
|
||||||
if option.unlimitedWorkers {
|
if option.unlimitedWorkers {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func (h *ConsistentHash) AddWithWeight(node any, weight int) {
|
|||||||
h.AddWithReplicas(node, replicas)
|
h.AddWithReplicas(node, replicas)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the corresponding node from h base on the given v.
|
// Get returns the corresponding node from h based on the given v.
|
||||||
func (h *ConsistentHash) Get(v any) (any, bool) {
|
func (h *ConsistentHash) Get(v any) (any, bool) {
|
||||||
h.lock.RLock()
|
h.lock.RLock()
|
||||||
defer h.lock.RUnlock()
|
defer h.lock.RUnlock()
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ type (
|
|||||||
gzip bool
|
gzip bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SizeLimitRotateRule a rotation rule that make the log file rotated base on size
|
// SizeLimitRotateRule a rotation rule that makes the log file rotated based on size
|
||||||
SizeLimitRotateRule struct {
|
SizeLimitRotateRule struct {
|
||||||
DailyRotateRule
|
DailyRotateRule
|
||||||
maxSize int64
|
maxSize int64
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// An Unstable is used to generate random value around the mean value base on given deviation.
|
// An Unstable is used to generate random value around the mean value based on given deviation.
|
||||||
type Unstable struct {
|
type Unstable struct {
|
||||||
deviation float64
|
deviation float64
|
||||||
r *rand.Rand
|
r *rand.Rand
|
||||||
|
|||||||
@@ -70,25 +70,16 @@ func getTaggedFieldValueMap(v reflect.Value) (map[string]any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getValueInterface(value reflect.Value) (any, error) {
|
func getValueInterface(value reflect.Value) (any, error) {
|
||||||
switch value.Kind() {
|
if !value.CanAddr() || !value.Addr().CanInterface() {
|
||||||
case reflect.Ptr:
|
return nil, ErrNotReadableValue
|
||||||
if !value.CanInterface() {
|
|
||||||
return nil, ErrNotReadableValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.IsNil() {
|
|
||||||
baseValueType := mapping.Deref(value.Type())
|
|
||||||
value.Set(reflect.New(baseValueType))
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Interface(), nil
|
|
||||||
default:
|
|
||||||
if !value.CanAddr() || !value.Addr().CanInterface() {
|
|
||||||
return nil, ErrNotReadableValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Addr().Interface(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if value.Kind() == reflect.Pointer && value.IsNil() {
|
||||||
|
baseValueType := mapping.Deref(value.Type())
|
||||||
|
value.Set(reflect.New(baseValueType))
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.Addr().Interface(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isScanFailed(err error) bool {
|
func isScanFailed(err error) bool {
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -1575,6 +1577,782 @@ func TestAnonymousStructPrError(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowsZeroValueStructPtr(t *testing.T) {
|
||||||
|
secondNamePtr := "second_ptr"
|
||||||
|
secondAgePtr := int64(30)
|
||||||
|
thirdNamePtr := "third_ptr"
|
||||||
|
thirdAgePtr := int64(0)
|
||||||
|
|
||||||
|
expect := []struct {
|
||||||
|
Name string
|
||||||
|
NamePtr *string
|
||||||
|
Age int64
|
||||||
|
AgePtr *int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "first",
|
||||||
|
NamePtr: nil,
|
||||||
|
Age: 2,
|
||||||
|
AgePtr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "second",
|
||||||
|
NamePtr: &secondNamePtr,
|
||||||
|
Age: 3,
|
||||||
|
AgePtr: &secondAgePtr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "",
|
||||||
|
NamePtr: &thirdNamePtr,
|
||||||
|
Age: 0,
|
||||||
|
AgePtr: &thirdAgePtr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var value []struct {
|
||||||
|
Age int64 `db:"age"`
|
||||||
|
AgePtr *int64 `db:"age_ptr"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
NamePtr *string `db:"name_ptr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{"name", "name_ptr", "age", "age_ptr"}).
|
||||||
|
AddRow("first", nil, 2, nil).
|
||||||
|
AddRow("second", "second_ptr", 3, 30).
|
||||||
|
AddRow("", "third_ptr", 0, 0)
|
||||||
|
|
||||||
|
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, name_ptr, age, age_ptr from users where user=?", "anyone"))
|
||||||
|
|
||||||
|
assert.Equal(t, 3, len(value), "应该返回3行数据")
|
||||||
|
|
||||||
|
for i, each := range expect {
|
||||||
|
|
||||||
|
assert.Equal(t, each.Name, value[i].Name)
|
||||||
|
assert.Equal(t, each.Age, value[i].Age)
|
||||||
|
|
||||||
|
if each.NamePtr == nil {
|
||||||
|
assert.Nil(t, value[i].NamePtr)
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, value[i].NamePtr)
|
||||||
|
assert.Equal(t, *each.NamePtr, *value[i].NamePtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if each.AgePtr == nil {
|
||||||
|
assert.Nil(t, value[i].AgePtr)
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, value[i].AgePtr)
|
||||||
|
assert.Equal(t, *each.AgePtr, *value[i].AgePtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowsAllNullStructPtrFields(t *testing.T) {
|
||||||
|
expect := []struct {
|
||||||
|
NamePtr *string
|
||||||
|
AgePtr *int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
NamePtr: nil,
|
||||||
|
AgePtr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NamePtr: stringPtr("second"),
|
||||||
|
AgePtr: int64Ptr(30),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NamePtr: nil,
|
||||||
|
AgePtr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var value []struct {
|
||||||
|
AgePtr *int64 `db:"age_ptr"`
|
||||||
|
NamePtr *string `db:"name_ptr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{"name_ptr", "age_ptr"}).
|
||||||
|
AddRow(nil, nil).
|
||||||
|
AddRow("second", 30).
|
||||||
|
AddRow(nil, nil)
|
||||||
|
|
||||||
|
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_ptr, age_ptr from users where user=?", "anyone"))
|
||||||
|
|
||||||
|
assert.Equal(t, 3, len(value))
|
||||||
|
|
||||||
|
for i, each := range expect {
|
||||||
|
if each.NamePtr == nil {
|
||||||
|
assert.Nil(t, value[i].NamePtr)
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, value[i].NamePtr)
|
||||||
|
assert.Equal(t, *each.NamePtr, *value[i].NamePtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if each.AgePtr == nil {
|
||||||
|
assert.Nil(t, value[i].AgePtr)
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, value[i].AgePtr)
|
||||||
|
assert.Equal(t, *each.AgePtr, *value[i].AgePtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowsWithSqlNullTypes(t *testing.T) {
|
||||||
|
expect := []struct {
|
||||||
|
Name string
|
||||||
|
NullName sql.NullString
|
||||||
|
Age int64
|
||||||
|
NullAge sql.NullInt64
|
||||||
|
Score float64
|
||||||
|
NullScore sql.NullFloat64
|
||||||
|
Active bool
|
||||||
|
NullActive sql.NullBool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "first",
|
||||||
|
NullName: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
Age: 20,
|
||||||
|
NullAge: sql.NullInt64{
|
||||||
|
Int64: 0,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
Score: 85.5,
|
||||||
|
NullScore: sql.NullFloat64{
|
||||||
|
Float64: 0,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
Active: true,
|
||||||
|
NullActive: sql.NullBool{
|
||||||
|
Bool: false,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "second",
|
||||||
|
NullName: sql.NullString{
|
||||||
|
String: "not_null_name",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Age: 25,
|
||||||
|
NullAge: sql.NullInt64{
|
||||||
|
Int64: 30,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Score: 90.0,
|
||||||
|
NullScore: sql.NullFloat64{
|
||||||
|
Float64: 95.5,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Active: false,
|
||||||
|
NullActive: sql.NullBool{
|
||||||
|
Bool: true,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "third",
|
||||||
|
NullName: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
Age: 0,
|
||||||
|
NullAge: sql.NullInt64{
|
||||||
|
Int64: 0,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
Score: 0,
|
||||||
|
NullScore: sql.NullFloat64{
|
||||||
|
Float64: 0,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
Active: false,
|
||||||
|
NullActive: sql.NullBool{
|
||||||
|
Bool: false,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var value []struct {
|
||||||
|
Name string `db:"name"`
|
||||||
|
NullName sql.NullString `db:"null_name"`
|
||||||
|
Age int64 `db:"age"`
|
||||||
|
NullAge sql.NullInt64 `db:"null_age"`
|
||||||
|
Score float64 `db:"score"`
|
||||||
|
NullScore sql.NullFloat64 `db:"null_score"`
|
||||||
|
Active bool `db:"active"`
|
||||||
|
NullActive sql.NullBool `db:"null_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{
|
||||||
|
"name", "null_name", "age", "null_age", "score", "null_score", "active", "null_active",
|
||||||
|
}).
|
||||||
|
AddRow("first", nil, 20, nil, 85.5, nil, true, nil).
|
||||||
|
AddRow("second", "not_null_name", 25, 30, 90.0, 95.5, false, true).
|
||||||
|
AddRow("third", nil, 0, nil, 0, nil, false, nil)
|
||||||
|
|
||||||
|
mock.ExpectQuery("select (.+) from users where type=?").
|
||||||
|
WithArgs("test").WillReturnRows(rs)
|
||||||
|
|
||||||
|
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||||
|
return unmarshalRows(&value, rows, true)
|
||||||
|
}, "select name, null_name, age, null_age, score, null_score, active, null_active from users where type=?", "test"))
|
||||||
|
|
||||||
|
assert.Equal(t, 3, len(value))
|
||||||
|
|
||||||
|
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.Score, value[i].Score)
|
||||||
|
assert.Equal(t, each.Active, value[i].Active)
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullName.Valid, value[i].NullName.Valid)
|
||||||
|
if each.NullName.Valid {
|
||||||
|
assert.Equal(t, each.NullName.String, value[i].NullName.String)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullAge.Valid, value[i].NullAge.Valid)
|
||||||
|
if each.NullAge.Valid {
|
||||||
|
assert.Equal(t, each.NullAge.Int64, value[i].NullAge.Int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullScore.Valid, value[i].NullScore.Valid)
|
||||||
|
if each.NullScore.Valid {
|
||||||
|
assert.Equal(t, each.NullScore.Float64, value[i].NullScore.Float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullActive.Valid, value[i].NullActive.Valid)
|
||||||
|
if each.NullActive.Valid {
|
||||||
|
assert.Equal(t, each.NullActive.Bool, value[i].NullActive.Bool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowsSqlNullWithMixedData(t *testing.T) {
|
||||||
|
expect := []struct {
|
||||||
|
Name string
|
||||||
|
NullName sql.NullString
|
||||||
|
Age int64
|
||||||
|
NullAge sql.NullInt64
|
||||||
|
IsStudent bool
|
||||||
|
NullActive sql.NullBool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "student1",
|
||||||
|
NullName: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
Age: 18,
|
||||||
|
NullAge: sql.NullInt64{
|
||||||
|
Int64: 0,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
IsStudent: true,
|
||||||
|
NullActive: sql.NullBool{
|
||||||
|
Bool: false,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "student2",
|
||||||
|
NullName: sql.NullString{
|
||||||
|
String: "has_nickname",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Age: 20,
|
||||||
|
NullAge: sql.NullInt64{
|
||||||
|
Int64: 22,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
IsStudent: false,
|
||||||
|
NullActive: sql.NullBool{
|
||||||
|
Bool: true,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var value []struct {
|
||||||
|
Name string `db:"name"`
|
||||||
|
NullName sql.NullString `db:"null_name"`
|
||||||
|
Age int64 `db:"age"`
|
||||||
|
NullAge sql.NullInt64 `db:"null_age"`
|
||||||
|
IsStudent bool `db:"is_student"`
|
||||||
|
NullActive sql.NullBool `db:"null_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{"name", "null_name", "age", "null_age", "is_student", "null_active"}).
|
||||||
|
AddRow("student1", nil, 18, nil, true, nil).
|
||||||
|
AddRow("student2", "has_nickname", 20, 22, false, true)
|
||||||
|
|
||||||
|
mock.ExpectQuery("select (.+) from students where class=?").
|
||||||
|
WithArgs("A").WillReturnRows(rs)
|
||||||
|
|
||||||
|
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||||
|
return unmarshalRows(&value, rows, true)
|
||||||
|
}, "select name, null_name, age, null_age, is_student, null_active from students where class=?", "A"))
|
||||||
|
|
||||||
|
assert.Equal(t, 2, len(value))
|
||||||
|
|
||||||
|
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.IsStudent, value[i].IsStudent)
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullName.Valid, value[i].NullName.Valid)
|
||||||
|
if each.NullName.Valid {
|
||||||
|
assert.Equal(t, each.NullName.String, value[i].NullName.String)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullAge.Valid, value[i].NullAge.Valid)
|
||||||
|
if each.NullAge.Valid {
|
||||||
|
assert.Equal(t, each.NullAge.Int64, value[i].NullAge.Int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullActive.Valid, value[i].NullActive.Valid)
|
||||||
|
if each.NullActive.Valid {
|
||||||
|
assert.Equal(t, each.NullActive.Bool, value[i].NullActive.Bool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowsSqlNullTime(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
futureTime := now.AddDate(1, 0, 0)
|
||||||
|
|
||||||
|
expect := []struct {
|
||||||
|
Name string
|
||||||
|
BirthDate sql.NullTime
|
||||||
|
LastLogin sql.NullTime
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "user1",
|
||||||
|
BirthDate: sql.NullTime{
|
||||||
|
Time: time.Time{},
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
LastLogin: sql.NullTime{
|
||||||
|
Time: now,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "user2",
|
||||||
|
BirthDate: sql.NullTime{
|
||||||
|
Time: futureTime,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
LastLogin: sql.NullTime{
|
||||||
|
Time: time.Time{},
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var value []struct {
|
||||||
|
Name string `db:"name"`
|
||||||
|
BirthDate sql.NullTime `db:"birth_date"`
|
||||||
|
LastLogin sql.NullTime `db:"last_login"`
|
||||||
|
}
|
||||||
|
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{"name", "birth_date", "last_login"}).
|
||||||
|
AddRow("user1", nil, now).
|
||||||
|
AddRow("user2", futureTime, nil)
|
||||||
|
|
||||||
|
mock.ExpectQuery("select (.+) from users").
|
||||||
|
WillReturnRows(rs)
|
||||||
|
|
||||||
|
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||||
|
return unmarshalRows(&value, rows, true)
|
||||||
|
}, "select name, birth_date, last_login from users"))
|
||||||
|
|
||||||
|
assert.Equal(t, 2, len(value))
|
||||||
|
|
||||||
|
for i, each := range expect {
|
||||||
|
assert.Equal(t, each.Name, value[i].Name)
|
||||||
|
|
||||||
|
assert.Equal(t, each.BirthDate.Valid, value[i].BirthDate.Valid)
|
||||||
|
if each.BirthDate.Valid {
|
||||||
|
assert.WithinDuration(t, each.BirthDate.Time, value[i].BirthDate.Time, time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, each.LastLogin.Valid, value[i].LastLogin.Valid)
|
||||||
|
if each.LastLogin.Valid {
|
||||||
|
assert.WithinDuration(t, each.LastLogin.Time, value[i].LastLogin.Time, time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowsSqlNullWithEmptyValues(t *testing.T) {
|
||||||
|
expect := []struct {
|
||||||
|
Name string
|
||||||
|
NullString sql.NullString
|
||||||
|
NullInt sql.NullInt64
|
||||||
|
NullFloat sql.NullFloat64
|
||||||
|
NullBool sql.NullBool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "empty_values",
|
||||||
|
NullString: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
NullInt: sql.NullInt64{
|
||||||
|
Int64: 0,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
NullFloat: sql.NullFloat64{
|
||||||
|
Float64: 0.0,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
NullBool: sql.NullBool{
|
||||||
|
Bool: false,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "null_values",
|
||||||
|
NullString: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
NullInt: sql.NullInt64{
|
||||||
|
Int64: 0,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
NullFloat: sql.NullFloat64{
|
||||||
|
Float64: 0.0,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
NullBool: sql.NullBool{
|
||||||
|
Bool: false,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "mixed_values",
|
||||||
|
NullString: sql.NullString{
|
||||||
|
String: "actual_value",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
NullInt: sql.NullInt64{
|
||||||
|
Int64: 0,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
NullFloat: sql.NullFloat64{
|
||||||
|
Float64: 0.0,
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
NullBool: sql.NullBool{
|
||||||
|
Bool: true,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var value []struct {
|
||||||
|
Name string `db:"name"`
|
||||||
|
NullString sql.NullString `db:"null_string"`
|
||||||
|
NullInt sql.NullInt64 `db:"null_int"`
|
||||||
|
NullFloat sql.NullFloat64 `db:"null_float"`
|
||||||
|
NullBool sql.NullBool `db:"null_bool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{"name", "null_string", "null_int", "null_float", "null_bool"}).
|
||||||
|
AddRow("empty_values", "", 0, 0.0, false).
|
||||||
|
AddRow("null_values", nil, nil, nil, nil).
|
||||||
|
AddRow("mixed_values", "actual_value", 0, nil, true)
|
||||||
|
|
||||||
|
mock.ExpectQuery("select (.+) from test_table").
|
||||||
|
WillReturnRows(rs)
|
||||||
|
|
||||||
|
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||||
|
return unmarshalRows(&value, rows, true)
|
||||||
|
}, "select name, null_string, null_int, null_float, null_bool from test_table"))
|
||||||
|
|
||||||
|
assert.Equal(t, 3, len(value))
|
||||||
|
|
||||||
|
for i, each := range expect {
|
||||||
|
|
||||||
|
assert.Equal(t, each.Name, value[i].Name)
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullString.Valid, value[i].NullString.Valid)
|
||||||
|
if each.NullString.Valid {
|
||||||
|
assert.Equal(t, each.NullString.String, value[i].NullString.String)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, "", value[i].NullString.String)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullInt.Valid, value[i].NullInt.Valid)
|
||||||
|
if each.NullInt.Valid {
|
||||||
|
assert.Equal(t, each.NullInt.Int64, value[i].NullInt.Int64)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, int64(0), value[i].NullInt.Int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullFloat.Valid, value[i].NullFloat.Valid)
|
||||||
|
if each.NullFloat.Valid {
|
||||||
|
assert.Equal(t, each.NullFloat.Float64, value[i].NullFloat.Float64)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, 0.0, value[i].NullFloat.Float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, each.NullBool.Valid, value[i].NullBool.Valid)
|
||||||
|
if each.NullBool.Valid {
|
||||||
|
assert.Equal(t, each.NullBool.Bool, value[i].NullBool.Bool)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, false, value[i].NullBool.Bool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowsSqlNullStringEmptyVsNull(t *testing.T) {
|
||||||
|
expect := []struct {
|
||||||
|
Name string
|
||||||
|
EmptyString sql.NullString
|
||||||
|
NullString sql.NullString
|
||||||
|
NormalString sql.NullString
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "row1",
|
||||||
|
EmptyString: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
NullString: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
NormalString: sql.NullString{
|
||||||
|
String: "hello",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "row2",
|
||||||
|
EmptyString: sql.NullString{
|
||||||
|
String: " ",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
NullString: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
NormalString: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var value []struct {
|
||||||
|
Name string `db:"name"`
|
||||||
|
EmptyString sql.NullString `db:"empty_string"`
|
||||||
|
NullString sql.NullString `db:"null_string"`
|
||||||
|
NormalString sql.NullString `db:"normal_string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{"name", "empty_string", "null_string", "normal_string"}).
|
||||||
|
AddRow("row1", "", nil, "hello").
|
||||||
|
AddRow("row2", " ", nil, "")
|
||||||
|
|
||||||
|
mock.ExpectQuery("select (.+) from string_test").
|
||||||
|
WillReturnRows(rs)
|
||||||
|
|
||||||
|
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||||
|
return unmarshalRows(&value, rows, true)
|
||||||
|
}, "select name, empty_string, null_string, normal_string from string_test"))
|
||||||
|
|
||||||
|
assert.Equal(t, 2, len(value))
|
||||||
|
|
||||||
|
for i, each := range expect {
|
||||||
|
assert.True(t, value[i].EmptyString.Valid)
|
||||||
|
assert.Equal(t, each.EmptyString.String, value[i].EmptyString.String)
|
||||||
|
|
||||||
|
assert.False(t, value[i].NullString.Valid)
|
||||||
|
assert.Equal(t, "", value[i].NullString.String)
|
||||||
|
|
||||||
|
assert.Equal(t, each.NormalString.Valid, value[i].NormalString.Valid)
|
||||||
|
if each.NormalString.Valid {
|
||||||
|
assert.Equal(t, each.NormalString.String, value[i].NormalString.String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetValueInterface(t *testing.T) {
|
||||||
|
t.Run("non_pointer_field", func(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
s := testStruct{}
|
||||||
|
v := reflect.ValueOf(&s).Elem()
|
||||||
|
|
||||||
|
nameField := v.Field(0)
|
||||||
|
result, err := getValueInterface(nameField)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
|
||||||
|
// Should return pointer to the field
|
||||||
|
ptr, ok := result.(*string)
|
||||||
|
assert.True(t, ok)
|
||||||
|
*ptr = "test"
|
||||||
|
assert.Equal(t, "test", s.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer_field_nil", func(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
NamePtr *string
|
||||||
|
AgePtr *int64
|
||||||
|
}
|
||||||
|
s := testStruct{}
|
||||||
|
v := reflect.ValueOf(&s).Elem()
|
||||||
|
|
||||||
|
// Test with nil pointer field
|
||||||
|
namePtrField := v.Field(0)
|
||||||
|
assert.True(t, namePtrField.IsNil(), "initial pointer should be nil")
|
||||||
|
|
||||||
|
result, err := getValueInterface(namePtrField)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
|
||||||
|
// Should have allocated the pointer
|
||||||
|
assert.False(t, namePtrField.IsNil(), "pointer should be allocated after getValueInterface")
|
||||||
|
|
||||||
|
// Should return pointer to pointer field
|
||||||
|
ptrPtr, ok := result.(**string)
|
||||||
|
assert.True(t, ok)
|
||||||
|
testValue := "initialized"
|
||||||
|
*ptrPtr = &testValue
|
||||||
|
assert.NotNil(t, s.NamePtr)
|
||||||
|
assert.Equal(t, "initialized", *s.NamePtr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer_field_already_allocated", func(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
NamePtr *string
|
||||||
|
}
|
||||||
|
initial := "existing"
|
||||||
|
s := testStruct{NamePtr: &initial}
|
||||||
|
v := reflect.ValueOf(&s).Elem()
|
||||||
|
|
||||||
|
namePtrField := v.Field(0)
|
||||||
|
assert.False(t, namePtrField.IsNil(), "pointer should not be nil initially")
|
||||||
|
|
||||||
|
result, err := getValueInterface(namePtrField)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
|
||||||
|
// Should return pointer to pointer field
|
||||||
|
ptrPtr, ok := result.(**string)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// Verify it points to the existing value
|
||||||
|
assert.Equal(t, "existing", **ptrPtr)
|
||||||
|
|
||||||
|
// Modify through the returned pointer
|
||||||
|
newValue := "modified"
|
||||||
|
*ptrPtr = &newValue
|
||||||
|
assert.Equal(t, "modified", *s.NamePtr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer_field_zero_value", func(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
IntPtr *int
|
||||||
|
}
|
||||||
|
s := testStruct{}
|
||||||
|
v := reflect.ValueOf(&s).Elem()
|
||||||
|
|
||||||
|
intPtrField := v.Field(0)
|
||||||
|
result, err := getValueInterface(intPtrField)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// After calling getValueInterface, nil pointer should be allocated
|
||||||
|
assert.NotNil(t, s.IntPtr)
|
||||||
|
|
||||||
|
// Set zero value through returned interface
|
||||||
|
ptrPtr, ok := result.(**int)
|
||||||
|
assert.True(t, ok)
|
||||||
|
zero := 0
|
||||||
|
*ptrPtr = &zero
|
||||||
|
assert.Equal(t, 0, *s.IntPtr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not_addressable_value", func(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
s := testStruct{Name: "test"}
|
||||||
|
v := reflect.ValueOf(s) // Non-pointer, not addressable
|
||||||
|
|
||||||
|
nameField := v.Field(0)
|
||||||
|
result, err := getValueInterface(nameField)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, ErrNotReadableValue, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple_pointer_types", func(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
StringPtr *string
|
||||||
|
IntPtr *int
|
||||||
|
Int64Ptr *int64
|
||||||
|
FloatPtr *float64
|
||||||
|
BoolPtr *bool
|
||||||
|
}
|
||||||
|
s := testStruct{}
|
||||||
|
v := reflect.ValueOf(&s).Elem()
|
||||||
|
|
||||||
|
// Test each pointer type gets properly initialized
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
assert.True(t, field.IsNil(), "field %d should start as nil", i)
|
||||||
|
|
||||||
|
result, err := getValueInterface(field)
|
||||||
|
assert.NoError(t, err, "field %d should not error", i)
|
||||||
|
assert.NotNil(t, result, "field %d result should not be nil", i)
|
||||||
|
|
||||||
|
// After getValueInterface, pointer should be allocated
|
||||||
|
assert.False(t, field.IsNil(), "field %d should be allocated", i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringPtr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64Ptr(i int64) *int64 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkIgnore(b *testing.B) {
|
func BenchmarkIgnore(b *testing.B) {
|
||||||
db, mock, err := sqlmock.New()
|
db, mock, err := sqlmock.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -21,7 +21,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15
|
go.etcd.io/etcd/api/v3 v3.5.15
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15
|
go.etcd.io/etcd/client/v3 v3.5.15
|
||||||
go.mongodb.org/mongo-driver/v2 v2.3.1
|
go.mongodb.org/mongo-driver/v2 v2.4.0
|
||||||
go.opentelemetry.io/otel v1.24.0
|
go.opentelemetry.io/otel v1.24.0
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -197,8 +197,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5
|
|||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
|
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.3.1 h1:WrCgSzO7dh1/FrePud9dK5fKNZOE97q5EQimGkos7Wo=
|
go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.3.1/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
||||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func AddProbe(probe Probe) {
|
|||||||
defaultHealthManager.addProbe(probe)
|
defaultHealthManager.addProbe(probe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHttpHandler create health http handler base on given probe.
|
// CreateHttpHandler creates a health http handler based on the given probe.
|
||||||
func CreateHttpHandler(healthResponse string) http.HandlerFunc {
|
func CreateHttpHandler(healthResponse string) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, _ *http.Request) {
|
return func(w http.ResponseWriter, _ *http.Request) {
|
||||||
if defaultHealthManager.IsReady() {
|
if defaultHealthManager.IsReady() {
|
||||||
|
|||||||
@@ -306,6 +306,8 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
|||||||
>108. 浙江银盾云科技有限公司
|
>108. 浙江银盾云科技有限公司
|
||||||
>109. 南京造世网络科技有限公司
|
>109. 南京造世网络科技有限公司
|
||||||
>110. 温州飞儿云信息技术有限公司
|
>110. 温州飞儿云信息技术有限公司
|
||||||
|
>111. 统信软件
|
||||||
|
>112. 深圳坐标软件集团有限公司
|
||||||
|
|
||||||
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func DocCommand(_ *cobra.Command, _ []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !pathx.FileExists(dir) {
|
if !pathx.FileExists(dir) {
|
||||||
return fmt.Errorf("dir %s not exsit", dir)
|
return fmt.Errorf("dir %s not exist", dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := filepath.Abs(dir)
|
dir, err := filepath.Abs(dir)
|
||||||
|
|||||||
@@ -38,13 +38,9 @@ func TestServerIntegration(t *testing.T) {
|
|||||||
ctx := svc.NewServiceContext(c)
|
ctx := svc.NewServiceContext(c)
|
||||||
handler.RegisterHandlers(server, ctx)
|
handler.RegisterHandlers(server, ctx)
|
||||||
|
|
||||||
// Start server in background
|
// Create serverless wrapper for testing
|
||||||
go func() {
|
serverless, err := rest.NewServerless(server)
|
||||||
server.Start()
|
require.NoError(t, err)
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait for server to start
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -56,7 +52,7 @@ func TestServerIntegration(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "health check",
|
name: "health check",
|
||||||
method: "GET",
|
method: http.MethodGet,
|
||||||
path: "/health",
|
path: "/health",
|
||||||
expectedStatus: http.StatusNotFound, // Adjust based on actual routes
|
expectedStatus: http.StatusNotFound, // Adjust based on actual routes
|
||||||
setup: func() {},
|
setup: func() {},
|
||||||
@@ -72,7 +68,7 @@ func TestServerIntegration(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{{end}}{{end}}{
|
{{end}}{{end}}{
|
||||||
name: "not found route",
|
name: "not found route",
|
||||||
method: "GET",
|
method: http.MethodGet,
|
||||||
path: "/nonexistent",
|
path: "/nonexistent",
|
||||||
expectedStatus: http.StatusNotFound,
|
expectedStatus: http.StatusNotFound,
|
||||||
setup: func() {},
|
setup: func() {},
|
||||||
@@ -87,7 +83,7 @@ func TestServerIntegration(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
server.ServeHTTP(rr, req)
|
serverless.Serve(rr, req)
|
||||||
|
|
||||||
assert.Equal(t, tt.expectedStatus, rr.Code)
|
assert.Equal(t, tt.expectedStatus, rr.Code)
|
||||||
|
|
||||||
|
|||||||
17
tools/goctl/api/gogen/jwt.api
Executable file
17
tools/goctl/api/gogen/jwt.api
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
type Request {
|
||||||
|
Name string `path:"name,options=you|me"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
@server(
|
||||||
|
jwt: Auth
|
||||||
|
jwtTransition: Trans
|
||||||
|
middleware: TokenValidate
|
||||||
|
)
|
||||||
|
service A-api {
|
||||||
|
@handler GreetHandler
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
package tsgen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGenWithInlineStructs(t *testing.T) {
|
|
||||||
// Create a temporary directory for the test
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
apiFile := filepath.Join(tmpDir, "test.api")
|
|
||||||
|
|
||||||
// Write the test API file
|
|
||||||
apiContent := `syntax = "v1"
|
|
||||||
|
|
||||||
info (
|
|
||||||
title: "Test ts generator"
|
|
||||||
desc: "Test inline struct handling"
|
|
||||||
author: "test"
|
|
||||||
version: "v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// common pagination request
|
|
||||||
type PaginationReq {
|
|
||||||
PageNum int ` + "`form:\"pageNum\"`" + `
|
|
||||||
PageSize int ` + "`form:\"pageSize\"`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
// base response
|
|
||||||
type BaseResp {
|
|
||||||
Code int64 ` + "`json:\"code\"`" + `
|
|
||||||
Msg string ` + "`json:\"msg\"`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
// common req
|
|
||||||
type GetListCommonReq {
|
|
||||||
Sth string ` + "`form:\"sth\"`" + `
|
|
||||||
PageNum int ` + "`form:\"pageNum\"`" + `
|
|
||||||
PageSize int ` + "`form:\"pageSize\"`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
// bad req to ts - inline struct with form tags
|
|
||||||
type GetListBadReq {
|
|
||||||
Sth string ` + "`form:\"sth\"`" + `
|
|
||||||
PaginationReq
|
|
||||||
}
|
|
||||||
|
|
||||||
// bad req to ts 2 - only inline struct with form tags
|
|
||||||
type GetListBad2Req {
|
|
||||||
PaginationReq
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetListResp - inline struct with json tags
|
|
||||||
type GetListResp {
|
|
||||||
BaseResp
|
|
||||||
}
|
|
||||||
|
|
||||||
service test-api {
|
|
||||||
@doc "common req"
|
|
||||||
@handler getListCommon
|
|
||||||
get /getListCommon (GetListCommonReq) returns (GetListResp)
|
|
||||||
|
|
||||||
@doc "bad req"
|
|
||||||
@handler getListBad
|
|
||||||
get /getListBad (GetListBadReq) returns (GetListResp)
|
|
||||||
|
|
||||||
@doc "bad req 2"
|
|
||||||
@handler getListBad2
|
|
||||||
get /getListBad2 (GetListBad2Req) returns (GetListResp)
|
|
||||||
|
|
||||||
@doc "no req"
|
|
||||||
@handler getListNoReq
|
|
||||||
get /getListNoReq returns (GetListResp)
|
|
||||||
}`
|
|
||||||
|
|
||||||
err := os.WriteFile(apiFile, []byte(apiContent), 0644)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Parse the API file
|
|
||||||
api, err := parser.Parse(apiFile)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Generate TypeScript files
|
|
||||||
outputDir := filepath.Join(tmpDir, "output")
|
|
||||||
err = os.MkdirAll(outputDir, 0755)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Generate the files directly
|
|
||||||
api.Service = api.Service.JoinPrefix()
|
|
||||||
err = genRequest(outputDir)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
err = genHandler(outputDir, ".", "webapi", api, false)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
err = genComponents(outputDir, api)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Read generated handler file
|
|
||||||
handlerFile := filepath.Join(outputDir, "test.ts")
|
|
||||||
handlerContent, err := os.ReadFile(handlerFile)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
handler := string(handlerContent)
|
|
||||||
|
|
||||||
// Read generated components file
|
|
||||||
componentsFile := filepath.Join(outputDir, "testComponents.ts")
|
|
||||||
componentsContent, err := os.ReadFile(componentsFile)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
components := string(componentsContent)
|
|
||||||
|
|
||||||
// Verify getListBad function signature and call
|
|
||||||
assert.Contains(t, handler, "export function getListBad(params: components.GetListBadReqParams)")
|
|
||||||
assert.Contains(t, handler, "return webapi.get<components.GetListResp>(`/getListBad`, params)")
|
|
||||||
// Should NOT contain 4 arguments
|
|
||||||
assert.NotContains(t, handler, "getListBad`, params, req, headers")
|
|
||||||
|
|
||||||
// Verify getListBad2 function signature and call
|
|
||||||
assert.Contains(t, handler, "export function getListBad2(params: components.GetListBad2ReqParams)")
|
|
||||||
assert.Contains(t, handler, "return webapi.get<components.GetListResp>(`/getListBad2`, params)")
|
|
||||||
// Should NOT reference non-existent headers
|
|
||||||
assert.NotContains(t, handler, "GetListBad2ReqHeaders")
|
|
||||||
|
|
||||||
// Verify getListCommon function signature and call
|
|
||||||
assert.Contains(t, handler, "export function getListCommon(params: components.GetListCommonReqParams)")
|
|
||||||
assert.Contains(t, handler, "return webapi.get<components.GetListResp>(`/getListCommon`, params)")
|
|
||||||
|
|
||||||
// Verify getListNoReq function signature and call
|
|
||||||
assert.Contains(t, handler, "export function getListNoReq()")
|
|
||||||
assert.Contains(t, handler, "return webapi.get<components.GetListResp>(`/getListNoReq`)")
|
|
||||||
|
|
||||||
// Verify GetListBadReqParams contains flattened fields
|
|
||||||
assert.Contains(t, components, "export interface GetListBadReqParams")
|
|
||||||
// Count occurrences of fields in GetListBadReqParams
|
|
||||||
paramsStart := strings.Index(components, "export interface GetListBadReqParams")
|
|
||||||
paramsEnd := strings.Index(components[paramsStart:], "}")
|
|
||||||
paramsSection := components[paramsStart : paramsStart+paramsEnd]
|
|
||||||
assert.Contains(t, paramsSection, "sth: string")
|
|
||||||
assert.Contains(t, paramsSection, "pageNum: number")
|
|
||||||
assert.Contains(t, paramsSection, "pageSize: number")
|
|
||||||
|
|
||||||
// Verify GetListBad2ReqParams contains flattened fields from inline PaginationReq
|
|
||||||
assert.Contains(t, components, "export interface GetListBad2ReqParams")
|
|
||||||
params2Start := strings.Index(components, "export interface GetListBad2ReqParams")
|
|
||||||
params2End := strings.Index(components[params2Start:], "}")
|
|
||||||
params2Section := components[params2Start : params2Start+params2End]
|
|
||||||
assert.Contains(t, params2Section, "pageNum: number")
|
|
||||||
assert.Contains(t, params2Section, "pageSize: number")
|
|
||||||
|
|
||||||
// Verify no empty Headers interfaces are generated
|
|
||||||
assert.NotContains(t, components, "GetListBadReqHeaders")
|
|
||||||
assert.NotContains(t, components, "GetListBad2ReqHeaders")
|
|
||||||
|
|
||||||
// Verify GetListResp contains flattened fields from BaseResp
|
|
||||||
assert.Contains(t, components, "export interface GetListResp")
|
|
||||||
respStart := strings.Index(components, "export interface GetListResp")
|
|
||||||
respEnd := strings.Index(components[respStart:], "}")
|
|
||||||
respSection := components[respStart : respStart+respEnd]
|
|
||||||
assert.Contains(t, respSection, "code: number")
|
|
||||||
assert.Contains(t, respSection, "msg: string")
|
|
||||||
}
|
|
||||||
@@ -212,7 +212,7 @@ func pathHasParams(route spec.Route) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasActualNonBodyMembers(ds)
|
return len(ds.Members) != len(ds.GetBodyMembers())
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRequestBody(route spec.Route) bool {
|
func hasRequestBody(route spec.Route) bool {
|
||||||
@@ -221,7 +221,7 @@ func hasRequestBody(route spec.Route) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(route.RequestTypeName()) > 0 && hasActualBodyMembers(ds)
|
return len(route.RequestTypeName()) > 0 && len(ds.GetBodyMembers()) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRequestPath(route spec.Route) bool {
|
func hasRequestPath(route spec.Route) bool {
|
||||||
@@ -230,7 +230,7 @@ func hasRequestPath(route spec.Route) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(route.RequestTypeName()) > 0 && hasActualTagMembers(ds, pathTagKey)
|
return len(route.RequestTypeName()) > 0 && len(ds.GetTagMembers(pathTagKey)) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRequestHeader(route spec.Route) bool {
|
func hasRequestHeader(route spec.Route) bool {
|
||||||
@@ -239,5 +239,5 @@ func hasRequestHeader(route spec.Route) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(route.RequestTypeName()) > 0 && hasActualTagMembers(ds, headerTagKey)
|
return len(route.RequestTypeName()) > 0 && len(ds.GetTagMembers(headerTagKey)) > 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,13 +164,13 @@ func writeType(writer io.Writer, tp spec.Type) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func genParamsTypesIfNeed(writer io.Writer, tp spec.Type) error {
|
func genParamsTypesIfNeed(writer io.Writer, tp spec.Type) error {
|
||||||
_, ok := tp.(spec.DefineStruct)
|
definedType, ok := tp.(spec.DefineStruct)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("no members of type " + tp.Name())
|
return errors.New("no members of type " + tp.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are actual non-body members (recursively through inline structs)
|
members := definedType.GetNonBodyMembers()
|
||||||
if !hasActualNonBodyMembers(tp) {
|
if len(members) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ func genParamsTypesIfNeed(writer io.Writer, tp spec.Type) error {
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(writer, "}\n")
|
fmt.Fprintf(writer, "}\n")
|
||||||
|
|
||||||
if hasActualTagMembers(tp, headerTagKey) {
|
if len(definedType.GetTagMembers(headerTagKey)) > 0 {
|
||||||
fmt.Fprintf(writer, "export interface %sHeaders {\n", util.Title(tp.Name()))
|
fmt.Fprintf(writer, "export interface %sHeaders {\n", util.Title(tp.Name()))
|
||||||
if err := writeTagMembers(writer, tp, headerTagKey); err != nil {
|
if err := writeTagMembers(writer, tp, headerTagKey); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -247,87 +247,3 @@ func writeTagMembers(writer io.Writer, tp spec.Type, tagKey string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasActualTagMembers checks if a type has actual members with the given tag,
|
|
||||||
// recursively checking inline/embedded structs
|
|
||||||
func hasActualTagMembers(tp spec.Type, tagKey string) bool {
|
|
||||||
definedType, ok := tp.(spec.DefineStruct)
|
|
||||||
if !ok {
|
|
||||||
pointType, ok := tp.(spec.PointerType)
|
|
||||||
if ok {
|
|
||||||
return hasActualTagMembers(pointType.Type, tagKey)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range definedType.Members {
|
|
||||||
if m.IsInline {
|
|
||||||
// Recursively check inline members
|
|
||||||
if hasActualTagMembers(m.Type, tagKey) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check non-inline members for the tag
|
|
||||||
if m.IsTagMember(tagKey) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasActualBodyMembers checks if a type has actual body members (json tags),
|
|
||||||
// recursively checking inline/embedded structs
|
|
||||||
func hasActualBodyMembers(tp spec.Type) bool {
|
|
||||||
definedType, ok := tp.(spec.DefineStruct)
|
|
||||||
if !ok {
|
|
||||||
pointType, ok := tp.(spec.PointerType)
|
|
||||||
if ok {
|
|
||||||
return hasActualBodyMembers(pointType.Type)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range definedType.Members {
|
|
||||||
if m.IsInline {
|
|
||||||
// Recursively check inline members
|
|
||||||
if hasActualBodyMembers(m.Type) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check non-inline members for json tag
|
|
||||||
if m.IsBodyMember() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasActualNonBodyMembers checks if a type has actual non-body members (form, path, header tags),
|
|
||||||
// recursively checking inline/embedded structs
|
|
||||||
func hasActualNonBodyMembers(tp spec.Type) bool {
|
|
||||||
definedType, ok := tp.(spec.DefineStruct)
|
|
||||||
if !ok {
|
|
||||||
pointType, ok := tp.(spec.PointerType)
|
|
||||||
if ok {
|
|
||||||
return hasActualNonBodyMembers(pointType.Type)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range definedType.Members {
|
|
||||||
if m.IsInline {
|
|
||||||
// Recursively check inline members
|
|
||||||
if hasActualNonBodyMembers(m.Type) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check non-inline members for non-body tags
|
|
||||||
if !m.IsBodyMember() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -37,268 +37,3 @@ func TestGenTsType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, `1 | 3 | 4 | 123`, ty)
|
assert.Equal(t, `1 | 3 | 4 | 123`, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasActualTagMembers(t *testing.T) {
|
|
||||||
// Test with no members
|
|
||||||
emptyStruct := spec.DefineStruct{
|
|
||||||
RawName: "Empty",
|
|
||||||
Members: []spec.Member{},
|
|
||||||
}
|
|
||||||
assert.False(t, hasActualTagMembers(emptyStruct, "form"))
|
|
||||||
assert.False(t, hasActualTagMembers(emptyStruct, "header"))
|
|
||||||
|
|
||||||
// Test with direct form members
|
|
||||||
directFormStruct := spec.DefineStruct{
|
|
||||||
RawName: "DirectForm",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "Field1",
|
|
||||||
Type: spec.PrimitiveType{RawName: "string"},
|
|
||||||
Tag: `form:"field1"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, hasActualTagMembers(directFormStruct, "form"))
|
|
||||||
assert.False(t, hasActualTagMembers(directFormStruct, "header"))
|
|
||||||
|
|
||||||
// Test with inline struct containing form members
|
|
||||||
inlineFormStruct := spec.DefineStruct{
|
|
||||||
RawName: "PaginationReq",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "PageNum",
|
|
||||||
Type: spec.PrimitiveType{RawName: "int"},
|
|
||||||
Tag: `form:"pageNum"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "PageSize",
|
|
||||||
Type: spec.PrimitiveType{RawName: "int"},
|
|
||||||
Tag: `form:"pageSize"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
parentStruct := spec.DefineStruct{
|
|
||||||
RawName: "ParentReq",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
Type: inlineFormStruct,
|
|
||||||
IsInline: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, hasActualTagMembers(parentStruct, "form"))
|
|
||||||
assert.False(t, hasActualTagMembers(parentStruct, "header"))
|
|
||||||
|
|
||||||
// Test with both direct and inline members
|
|
||||||
mixedStruct := spec.DefineStruct{
|
|
||||||
RawName: "MixedReq",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "Sth",
|
|
||||||
Type: spec.PrimitiveType{RawName: "string"},
|
|
||||||
Tag: `form:"sth"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
Type: inlineFormStruct,
|
|
||||||
IsInline: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, hasActualTagMembers(mixedStruct, "form"))
|
|
||||||
assert.False(t, hasActualTagMembers(mixedStruct, "header"))
|
|
||||||
|
|
||||||
// Test with inline struct containing only json members (body members)
|
|
||||||
inlineJsonStruct := spec.DefineStruct{
|
|
||||||
RawName: "JsonStruct",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "Code",
|
|
||||||
Type: spec.PrimitiveType{RawName: "int64"},
|
|
||||||
Tag: `json:"code"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Msg",
|
|
||||||
Type: spec.PrimitiveType{RawName: "string"},
|
|
||||||
Tag: `json:"msg"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
parentJsonStruct := spec.DefineStruct{
|
|
||||||
RawName: "ParentResp",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
Type: inlineJsonStruct,
|
|
||||||
IsInline: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.False(t, hasActualTagMembers(parentJsonStruct, "form"))
|
|
||||||
assert.False(t, hasActualTagMembers(parentJsonStruct, "header"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasActualBodyMembers(t *testing.T) {
|
|
||||||
// Test with no members
|
|
||||||
emptyStruct := spec.DefineStruct{
|
|
||||||
RawName: "Empty",
|
|
||||||
Members: []spec.Member{},
|
|
||||||
}
|
|
||||||
assert.False(t, hasActualBodyMembers(emptyStruct))
|
|
||||||
|
|
||||||
// Test with direct json members
|
|
||||||
directJsonStruct := spec.DefineStruct{
|
|
||||||
RawName: "DirectJson",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "Code",
|
|
||||||
Type: spec.PrimitiveType{RawName: "int64"},
|
|
||||||
Tag: `json:"code"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, hasActualBodyMembers(directJsonStruct))
|
|
||||||
|
|
||||||
// Test with inline struct containing json members
|
|
||||||
inlineJsonStruct := spec.DefineStruct{
|
|
||||||
RawName: "BaseResp",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "Code",
|
|
||||||
Type: spec.PrimitiveType{RawName: "int64"},
|
|
||||||
Tag: `json:"code"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Msg",
|
|
||||||
Type: spec.PrimitiveType{RawName: "string"},
|
|
||||||
Tag: `json:"msg"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
parentStruct := spec.DefineStruct{
|
|
||||||
RawName: "ParentResp",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
Type: inlineJsonStruct,
|
|
||||||
IsInline: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, hasActualBodyMembers(parentStruct))
|
|
||||||
|
|
||||||
// Test with inline struct containing only form members (not body members)
|
|
||||||
inlineFormStruct := spec.DefineStruct{
|
|
||||||
RawName: "PaginationReq",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "PageNum",
|
|
||||||
Type: spec.PrimitiveType{RawName: "int"},
|
|
||||||
Tag: `form:"pageNum"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
parentFormStruct := spec.DefineStruct{
|
|
||||||
RawName: "ParentReq",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
Type: inlineFormStruct,
|
|
||||||
IsInline: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.False(t, hasActualBodyMembers(parentFormStruct))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasActualNonBodyMembers(t *testing.T) {
|
|
||||||
// Test with no members
|
|
||||||
emptyStruct := spec.DefineStruct{
|
|
||||||
RawName: "Empty",
|
|
||||||
Members: []spec.Member{},
|
|
||||||
}
|
|
||||||
assert.False(t, hasActualNonBodyMembers(emptyStruct))
|
|
||||||
|
|
||||||
// Test with direct form members
|
|
||||||
directFormStruct := spec.DefineStruct{
|
|
||||||
RawName: "DirectForm",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "Field1",
|
|
||||||
Type: spec.PrimitiveType{RawName: "string"},
|
|
||||||
Tag: `form:"field1"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, hasActualNonBodyMembers(directFormStruct))
|
|
||||||
|
|
||||||
// Test with inline struct containing form members
|
|
||||||
inlineFormStruct := spec.DefineStruct{
|
|
||||||
RawName: "PaginationReq",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "PageNum",
|
|
||||||
Type: spec.PrimitiveType{RawName: "int"},
|
|
||||||
Tag: `form:"pageNum"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "PageSize",
|
|
||||||
Type: spec.PrimitiveType{RawName: "int"},
|
|
||||||
Tag: `form:"pageSize"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
parentStruct := spec.DefineStruct{
|
|
||||||
RawName: "ParentReq",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
Type: inlineFormStruct,
|
|
||||||
IsInline: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, hasActualNonBodyMembers(parentStruct))
|
|
||||||
|
|
||||||
// Test with inline struct containing only json members (body members)
|
|
||||||
inlineJsonStruct := spec.DefineStruct{
|
|
||||||
RawName: "BaseResp",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "Code",
|
|
||||||
Type: spec.PrimitiveType{RawName: "int64"},
|
|
||||||
Tag: `json:"code"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
parentJsonStruct := spec.DefineStruct{
|
|
||||||
RawName: "ParentResp",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
Type: inlineJsonStruct,
|
|
||||||
IsInline: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.False(t, hasActualNonBodyMembers(parentJsonStruct))
|
|
||||||
|
|
||||||
// Test with both direct and inline non-body members
|
|
||||||
mixedStruct := spec.DefineStruct{
|
|
||||||
RawName: "MixedReq",
|
|
||||||
Members: []spec.Member{
|
|
||||||
{
|
|
||||||
Name: "Sth",
|
|
||||||
Type: spec.PrimitiveType{RawName: "string"},
|
|
||||||
Tag: `form:"sth"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
Type: inlineFormStruct,
|
|
||||||
IsInline: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.True(t, hasActualNonBodyMembers(mixedStruct))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
## swagger
|
## swagger
|
||||||
1. [bug fix] remove example generation when request body are `query`, `path` and `header`
|
1. [bug fix] remove example generation when request body are `query`, `path` and `header`
|
||||||
- it not supported in api spec 2.0
|
- it not supported in api spec 2.0
|
||||||
- it's will generate example when request body is json format.
|
- it will generate example when request body is json format.
|
||||||
2. [features] swagger generation supported definitions
|
2. [features] swagger generation supported definitions
|
||||||
- supported response definitions
|
- supported response definitions
|
||||||
- supported json request body definitions
|
- supported json request body definitions
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ var (
|
|||||||
WithDialOption = internal.WithDialOption
|
WithDialOption = internal.WithDialOption
|
||||||
// WithNonBlock sets the dialing to be nonblock.
|
// WithNonBlock sets the dialing to be nonblock.
|
||||||
WithNonBlock = internal.WithNonBlock
|
WithNonBlock = internal.WithNonBlock
|
||||||
|
// WithBlock sets the dialing to be blocking.
|
||||||
|
// Deprecated: blocking dials are not recommended by gRPC.
|
||||||
|
WithBlock = internal.WithBlock
|
||||||
// WithStreamClientInterceptor is an alias of internal.WithStreamClientInterceptor.
|
// WithStreamClientInterceptor is an alias of internal.WithStreamClientInterceptor.
|
||||||
WithStreamClientInterceptor = internal.WithStreamClientInterceptor
|
WithStreamClientInterceptor = internal.WithStreamClientInterceptor
|
||||||
// WithTimeout is an alias of internal.WithTimeout.
|
// WithTimeout is an alias of internal.WithTimeout.
|
||||||
@@ -61,6 +64,8 @@ func NewClient(c RpcClientConf, options ...ClientOption) (Client, error) {
|
|||||||
}
|
}
|
||||||
if c.NonBlock {
|
if c.NonBlock {
|
||||||
opts = append(opts, WithNonBlock())
|
opts = append(opts, WithNonBlock())
|
||||||
|
} else {
|
||||||
|
opts = append(opts, WithBlock())
|
||||||
}
|
}
|
||||||
if c.Timeout > 0 {
|
if c.Timeout > 0 {
|
||||||
opts = append(opts, WithTimeout(time.Duration(c.Timeout)*time.Millisecond))
|
opts = append(opts, WithTimeout(time.Duration(c.Timeout)*time.Millisecond))
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ type (
|
|||||||
Target string `json:",optional"`
|
Target string `json:",optional"`
|
||||||
App string `json:",optional"`
|
App string `json:",optional"`
|
||||||
Token string `json:",optional"`
|
Token string `json:",optional"`
|
||||||
NonBlock bool `json:",optional"`
|
NonBlock bool `json:",default=true"`
|
||||||
Timeout int64 `json:",default=2000"`
|
Timeout int64 `json:",default=2000"`
|
||||||
KeepaliveTime time.Duration `json:",optional"`
|
KeepaliveTime time.Duration `json:",optional"`
|
||||||
Middlewares ClientMiddlewaresConf
|
Middlewares ClientMiddlewaresConf
|
||||||
|
|||||||
@@ -141,6 +141,15 @@ func (c *client) dial(server string, opts ...ClientOption) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithBlock sets the dialing to be blocking.
|
||||||
|
// Deprecated: blocking dials are not recommended by gRPC.
|
||||||
|
// See https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md
|
||||||
|
func WithBlock() ClientOption {
|
||||||
|
return func(options *ClientOptions) {
|
||||||
|
options.NonBlock = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithDialOption returns a func to customize a ClientOptions with given dial option.
|
// WithDialOption returns a func to customize a ClientOptions with given dial option.
|
||||||
func WithDialOption(opt grpc.DialOption) ClientOption {
|
func WithDialOption(opt grpc.DialOption) ClientOption {
|
||||||
return func(options *ClientOptions) {
|
return func(options *ClientOptions) {
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ func TestWithNonBlock(t *testing.T) {
|
|||||||
assert.True(t, options.NonBlock)
|
assert.True(t, options.NonBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithBlock(t *testing.T) {
|
||||||
|
var options ClientOptions
|
||||||
|
opt := WithBlock()
|
||||||
|
opt(&options)
|
||||||
|
assert.False(t, options.NonBlock)
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithStreamClientInterceptor(t *testing.T) {
|
func TestWithStreamClientInterceptor(t *testing.T) {
|
||||||
var options ClientOptions
|
var options ClientOptions
|
||||||
opt := WithStreamClientInterceptor(func(ctx context.Context, desc *grpc.StreamDesc,
|
opt := WithStreamClientInterceptor(func(ctx context.Context, desc *grpc.StreamDesc,
|
||||||
|
|||||||
Reference in New Issue
Block a user