mirror of
https://gitee.com/johng/gf
synced 2026-06-08 02:27:42 +08:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a8b33fa09 | |||
| 5884a0e05f | |||
| 31e44062a8 | |||
| 87cb1c9b8e | |||
| 0266d24d0a | |||
| 0876e00eb8 | |||
| 85c4794ceb | |||
| e007bf35b2 | |||
| 74e968e93b | |||
| 18507fb836 | |||
| 3b245837b9 | |||
| a853984f52 | |||
| 00c544ee99 | |||
| e7b9e41a5e | |||
| e254b4f3c0 | |||
| b0c9c68c9c | |||
| 1030434ce6 | |||
| 2f08c4b00f |
3
.github/workflows/build_and_test.sh
vendored
3
.github/workflows/build_and_test.sh
vendored
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
GOARCH=${{ matrix.goarch }}
|
||||
for file in `find . -name go.mod`; do
|
||||
dirpath=$(dirname $file)
|
||||
echo $dirpath
|
||||
@ -41,8 +40,10 @@ for file in `find . -name go.mod`; do
|
||||
go mod tidy
|
||||
go build ./...
|
||||
go test ./... -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./...,github.com/gogf/gf/... || exit 1
|
||||
|
||||
if grep -q "/gogf/gf/.*/v2" go.mod; then
|
||||
sed -i "s/gogf\/gf\(\/.*\)\/v2/gogf\/gf\/v2\1/g" coverage.out
|
||||
fi
|
||||
|
||||
cd -
|
||||
done
|
||||
@ -59,10 +59,10 @@ func NewArrayRange(start, end, step int, safe ...bool) *Array {
|
||||
if step == 0 {
|
||||
panic(fmt.Sprintf(`invalid step value: %d`, step))
|
||||
}
|
||||
slice := make([]interface{}, (end-start+1)/step)
|
||||
slice := make([]interface{}, 0)
|
||||
index := 0
|
||||
for i := start; i <= end; i += step {
|
||||
slice[index] = i
|
||||
slice = append(slice, i)
|
||||
index++
|
||||
}
|
||||
return NewArrayFrom(slice, safe...)
|
||||
|
||||
@ -51,10 +51,10 @@ func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray {
|
||||
if step == 0 {
|
||||
panic(fmt.Sprintf(`invalid step value: %d`, step))
|
||||
}
|
||||
slice := make([]int, (end-start+1)/step)
|
||||
slice := make([]int, 0)
|
||||
index := 0
|
||||
for i := start; i <= end; i += step {
|
||||
slice[index] = i
|
||||
slice = append(slice, i)
|
||||
index++
|
||||
}
|
||||
return NewIntArrayFrom(slice, safe...)
|
||||
|
||||
@ -61,10 +61,10 @@ func NewSortedArrayRange(start, end, step int, comparator func(a, b interface{})
|
||||
if step == 0 {
|
||||
panic(fmt.Sprintf(`invalid step value: %d`, step))
|
||||
}
|
||||
slice := make([]interface{}, (end-start+1)/step)
|
||||
slice := make([]interface{}, 0)
|
||||
index := 0
|
||||
for i := start; i <= end; i += step {
|
||||
slice[index] = i
|
||||
slice = append(slice, i)
|
||||
index++
|
||||
}
|
||||
return NewSortedArrayFrom(slice, comparator, safe...)
|
||||
|
||||
@ -62,10 +62,10 @@ func NewSortedIntArrayRange(start, end, step int, safe ...bool) *SortedIntArray
|
||||
if step == 0 {
|
||||
panic(fmt.Sprintf(`invalid step value: %d`, step))
|
||||
}
|
||||
slice := make([]int, (end-start+1)/step)
|
||||
slice := make([]int, 0)
|
||||
index := 0
|
||||
for i := start; i <= end; i += step {
|
||||
slice[index] = i
|
||||
slice = append(slice, i)
|
||||
index++
|
||||
}
|
||||
return NewSortedIntArrayFrom(slice, safe...)
|
||||
|
||||
@ -59,7 +59,7 @@ func ExampleNewIntArrayRange() {
|
||||
fmt.Println(s.Slice(), s.Len(), cap(s.Slice()))
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5] 5 5
|
||||
// [1 2 3 4 5] 5 8
|
||||
}
|
||||
|
||||
func ExampleNewIntArrayFrom() {
|
||||
|
||||
@ -813,3 +813,14 @@ func TestIntArray_Walk(t *testing.T) {
|
||||
}), g.Slice{11, 12})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_NewIntArrayRange(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewIntArrayRange(0, 128, 4)
|
||||
t.Assert(array.String(), `[0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128]`)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewIntArrayRange(1, 128, 4)
|
||||
t.Assert(array.String(), `[1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93,97,101,105,109,113,117,121,125]`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ import (
|
||||
// Queue is a concurrent-safe queue built on doubly linked list and channel.
|
||||
type Queue struct {
|
||||
limit int // Limit for queue size.
|
||||
length *gtype.Int64 // Queue length.
|
||||
list *glist.List // Underlying list structure for data maintaining.
|
||||
closed *gtype.Bool // Whether queue is closed.
|
||||
events chan struct{} // Events for data writing.
|
||||
@ -44,6 +45,7 @@ const (
|
||||
func New(limit ...int) *Queue {
|
||||
q := &Queue{
|
||||
closed: gtype.NewBool(),
|
||||
length: gtype.NewInt64(),
|
||||
}
|
||||
if len(limit) > 0 && limit[0] > 0 {
|
||||
q.limit = limit[0]
|
||||
@ -57,6 +59,57 @@ func New(limit ...int) *Queue {
|
||||
return q
|
||||
}
|
||||
|
||||
// Push pushes the data `v` into the queue.
|
||||
// Note that it would panic if Push is called after the queue is closed.
|
||||
func (q *Queue) Push(v interface{}) {
|
||||
q.length.Add(1)
|
||||
if q.limit > 0 {
|
||||
q.C <- v
|
||||
} else {
|
||||
q.list.PushBack(v)
|
||||
if len(q.events) < defaultQueueSize {
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop pops an item from the queue in FIFO way.
|
||||
// Note that it would return nil immediately if Pop is called after the queue is closed.
|
||||
func (q *Queue) Pop() interface{} {
|
||||
item := <-q.C
|
||||
q.length.Add(-1)
|
||||
return item
|
||||
}
|
||||
|
||||
// Close closes the queue.
|
||||
// Notice: It would notify all goroutines return immediately,
|
||||
// which are being blocked reading using Pop method.
|
||||
func (q *Queue) Close() {
|
||||
q.closed.Set(true)
|
||||
if q.events != nil {
|
||||
close(q.events)
|
||||
}
|
||||
if q.limit > 0 {
|
||||
close(q.C)
|
||||
} else {
|
||||
for i := 0; i < defaultBatchSize; i++ {
|
||||
q.Pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the length of the queue.
|
||||
// Note that the result might not be accurate as there's an
|
||||
// asynchronous channel reading the list constantly.
|
||||
func (q *Queue) Len() (length int64) {
|
||||
return q.length.Val()
|
||||
}
|
||||
|
||||
// Size is alias of Len.
|
||||
func (q *Queue) Size() int64 {
|
||||
return q.Len()
|
||||
}
|
||||
|
||||
// asyncLoopFromListToChannel starts an asynchronous goroutine,
|
||||
// which handles the data synchronization from list `q.list` to channel `q.C`.
|
||||
func (q *Queue) asyncLoopFromListToChannel() {
|
||||
@ -90,55 +143,3 @@ func (q *Queue) asyncLoopFromListToChannel() {
|
||||
// It's the sender's responsibility to close channel when it should be closed.
|
||||
close(q.C)
|
||||
}
|
||||
|
||||
// Push pushes the data `v` into the queue.
|
||||
// Note that it would panic if Push is called after the queue is closed.
|
||||
func (q *Queue) Push(v interface{}) {
|
||||
if q.limit > 0 {
|
||||
q.C <- v
|
||||
} else {
|
||||
q.list.PushBack(v)
|
||||
if len(q.events) < defaultQueueSize {
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop pops an item from the queue in FIFO way.
|
||||
// Note that it would return nil immediately if Pop is called after the queue is closed.
|
||||
func (q *Queue) Pop() interface{} {
|
||||
return <-q.C
|
||||
}
|
||||
|
||||
// Close closes the queue.
|
||||
// Notice: It would notify all goroutines return immediately,
|
||||
// which are being blocked reading using Pop method.
|
||||
func (q *Queue) Close() {
|
||||
q.closed.Set(true)
|
||||
if q.events != nil {
|
||||
close(q.events)
|
||||
}
|
||||
if q.limit > 0 {
|
||||
close(q.C)
|
||||
} else {
|
||||
for i := 0; i < defaultBatchSize; i++ {
|
||||
q.Pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the length of the queue.
|
||||
// Note that the result might not be accurate as there's an
|
||||
// asynchronous channel reading the list constantly.
|
||||
func (q *Queue) Len() (length int) {
|
||||
if q.list != nil {
|
||||
length += q.list.Len()
|
||||
}
|
||||
length += len(q.C)
|
||||
return
|
||||
}
|
||||
|
||||
// Size is alias of Len.
|
||||
func (q *Queue) Size() int {
|
||||
return q.Len()
|
||||
}
|
||||
|
||||
@ -94,14 +94,14 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
} else {
|
||||
if config.Pass != "" {
|
||||
source = fmt.Sprintf(
|
||||
"clickhouse://%s:%s@%s:%s/%s?charset=%s&debug=%t",
|
||||
"clickhouse://%s:%s@%s:%s/%s?debug=%t",
|
||||
config.User, url.PathEscape(config.Pass),
|
||||
config.Host, config.Port, config.Name, config.Charset, config.Debug,
|
||||
config.Host, config.Port, config.Name, config.Debug,
|
||||
)
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"clickhouse://%s@%s:%s/%s?charset=%s&debug=%t",
|
||||
config.User, config.Host, config.Port, config.Name, config.Charset, config.Debug,
|
||||
"clickhouse://%s@%s:%s/%s?debug=%t",
|
||||
config.User, config.Host, config.Port, config.Name, config.Debug,
|
||||
)
|
||||
}
|
||||
if config.Extra != "" {
|
||||
@ -173,8 +173,12 @@ func (d *Driver) TableFields(
|
||||
isNull = true
|
||||
fieldType = fieldsResult[1]
|
||||
}
|
||||
position := m["position"].Int()
|
||||
if result[0]["position"].Int() != 0 {
|
||||
position -= 1
|
||||
}
|
||||
fields[m["name"].String()] = &gdb.TableField{
|
||||
Index: m["position"].Int(),
|
||||
Index: position,
|
||||
Name: m["name"].String(),
|
||||
Default: m["default_expression"].Val(),
|
||||
Comment: m["comment"].String(),
|
||||
|
||||
@ -42,7 +42,7 @@ func Test_Model_Raw(t *testing.T) {
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).Raw(fmt.Sprintf("select id from %s ", table)).Count()
|
||||
t.Assert(count, int64(10))
|
||||
t.Assert(count, 10)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
|
||||
@ -249,12 +249,12 @@ func Test_Model_Count(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, int64(TableSize))
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).FieldsEx("id").Where("id>8").Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, int64(2))
|
||||
t.Assert(count, 2)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -283,9 +283,9 @@ func TestDriverClickhouse_Insert(t *testing.T) {
|
||||
Created time.Time `orm:"created"`
|
||||
}
|
||||
var (
|
||||
insertUrl = "https://goframe.org"
|
||||
total int64 = 0
|
||||
item = insertItem{
|
||||
insertUrl = "https://goframe.org"
|
||||
total = 0
|
||||
item = insertItem{
|
||||
Duration: 1,
|
||||
Url: insertUrl,
|
||||
Created: time.Now(),
|
||||
@ -297,7 +297,7 @@ func TestDriverClickhouse_Insert(t *testing.T) {
|
||||
gtest.AssertNil(err)
|
||||
total, err = connect.Model("visits").Count()
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(total, int64(2))
|
||||
gtest.AssertEQ(total, 2)
|
||||
var list []*insertItem
|
||||
for i := 0; i < 50; i++ {
|
||||
list = append(list, &insertItem{
|
||||
@ -312,7 +312,7 @@ func TestDriverClickhouse_Insert(t *testing.T) {
|
||||
gtest.AssertNil(err)
|
||||
total, err = connect.Model("visits").Count()
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(total, int64(102))
|
||||
gtest.AssertEQ(total, 102)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Insert_Use_Exec(t *testing.T) {
|
||||
@ -467,7 +467,7 @@ func TestDriverClickhouse_NilTime(t *testing.T) {
|
||||
gtest.AssertNil(err)
|
||||
count, err := connect.Model("data_type").Where("Col4", "Inc.").Count()
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(count, int64(10000))
|
||||
gtest.AssertEQ(count, 10000)
|
||||
|
||||
data, err := connect.Model("data_type").Where("Col4", "Inc.").One()
|
||||
gtest.AssertNil(err)
|
||||
@ -508,7 +508,7 @@ func TestDriverClickhouse_BatchInsert(t *testing.T) {
|
||||
gtest.AssertNil(err)
|
||||
count, err := connect.Model("data_type").Where("Col2", "ClickHouse").Where("Col3", "Inc").Count()
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(count, int64(10000))
|
||||
gtest.AssertEQ(count, 10000)
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_Open(t *testing.T) {
|
||||
@ -533,18 +533,18 @@ func TestDriverClickhouse_TableFields(t *testing.T) {
|
||||
gtest.AssertNE(dataTypeTable, nil)
|
||||
|
||||
var result = map[string][]interface{}{
|
||||
"Col1": {1, "Col1", "UInt8", false, "", "", "", "列1"},
|
||||
"Col2": {2, "Col2", "String", true, "", "", "", "列2"},
|
||||
"Col3": {3, "Col3", "FixedString(3)", false, "", "", "", "列3"},
|
||||
"Col4": {4, "Col4", "String", false, "", "", "", "列4"},
|
||||
"Col5": {5, "Col5", "Map(String, UInt8)", false, "", "", "", "列5"},
|
||||
"Col6": {6, "Col6", "Array(String)", false, "", "", "", "列6"},
|
||||
"Col7": {7, "Col7", "Tuple(String, UInt8, Array(Map(String, String)))", false, "", "", "", "列7"},
|
||||
"Col8": {8, "Col8", "DateTime", false, "", "", "", "列8"},
|
||||
"Col9": {9, "Col9", "UUID", false, "", "", "", "列9"},
|
||||
"Col10": {10, "Col10", "DateTime", false, "", "", "", "列10"},
|
||||
"Col11": {11, "Col11", "Decimal(9, 2)", false, "", "", "", "列11"},
|
||||
"Col12": {12, "Col12", "Decimal(9, 2)", false, "", "", "", "列12"},
|
||||
"Col1": {0, "Col1", "UInt8", false, "", "", "", "列1"},
|
||||
"Col2": {1, "Col2", "String", true, "", "", "", "列2"},
|
||||
"Col3": {2, "Col3", "FixedString(3)", false, "", "", "", "列3"},
|
||||
"Col4": {3, "Col4", "String", false, "", "", "", "列4"},
|
||||
"Col5": {4, "Col5", "Map(String, UInt8)", false, "", "", "", "列5"},
|
||||
"Col6": {5, "Col6", "Array(String)", false, "", "", "", "列6"},
|
||||
"Col7": {6, "Col7", "Tuple(String, UInt8, Array(Map(String, String)))", false, "", "", "", "列7"},
|
||||
"Col8": {7, "Col8", "DateTime", false, "", "", "", "列8"},
|
||||
"Col9": {8, "Col9", "UUID", false, "", "", "", "列9"},
|
||||
"Col10": {9, "Col10", "DateTime", false, "", "", "", "列10"},
|
||||
"Col11": {10, "Col11", "Decimal(9, 2)", false, "", "", "", "列11"},
|
||||
"Col12": {11, "Col12", "Decimal(9, 2)", false, "", "", "", "列12"},
|
||||
}
|
||||
for k, v := range result {
|
||||
_, ok := dataTypeTable[k]
|
||||
@ -558,3 +558,13 @@ func TestDriverClickhouse_TableFields(t *testing.T) {
|
||||
gtest.AssertEQ(dataTypeTable[k].Comment, v[7])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverClickhouse_TableFields_HasField(t *testing.T) {
|
||||
connect := clickhouseConfigDB()
|
||||
gtest.AssertNil(createClickhouseExampleTable(connect))
|
||||
defer dropClickhouseExampleTable(connect)
|
||||
// 未修复前:panic: runtime error: index out of range [12] with length 12
|
||||
b, err := connect.GetCore().HasField(context.Background(), "data_type", "Col1")
|
||||
gtest.AssertNil(err)
|
||||
gtest.AssertEQ(b, true)
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ func Test_Master_Slave(t *testing.T) {
|
||||
_, err = masterSlaveDB.Model(table).Data(array).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
var count int64
|
||||
var count int
|
||||
// Auto slave.
|
||||
count, err = masterSlaveDB.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
@ -456,3 +457,22 @@ func Test_Issue2105(t *testing.T) {
|
||||
t.Assert(len(list[1].Json), 3)
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2231
|
||||
func Test_Issue2231(t *testing.T) {
|
||||
linkPattern := `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)`
|
||||
link := `mysql:root:12345678@tcp(127.0.0.1:3306)/a正bc式?loc=Local&parseTime=true`
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
match, err := gregex.MatchString(linkPattern, link)
|
||||
t.AssertNil(err)
|
||||
t.Assert(match[1], "mysql")
|
||||
t.Assert(match[2], "root")
|
||||
t.Assert(match[3], "12345678")
|
||||
t.Assert(match[4], "tcp")
|
||||
t.Assert(match[5], "127.0.0.1:3306")
|
||||
t.Assert(match[6], "a正bc式")
|
||||
t.Assert(match[7], "loc=Local&parseTime=true")
|
||||
})
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@ type DB interface {
|
||||
GetOne(ctx context.Context, sql string, args ...interface{}) (Record, error) // See Core.GetOne.
|
||||
GetValue(ctx context.Context, sql string, args ...interface{}) (Value, error) // See Core.GetValue.
|
||||
GetArray(ctx context.Context, sql string, args ...interface{}) ([]Value, error) // See Core.GetArray.
|
||||
GetCount(ctx context.Context, sql string, args ...interface{}) (int64, error) // See Core.GetCount.
|
||||
GetCount(ctx context.Context, sql string, args ...interface{}) (int, error) // See Core.GetCount.
|
||||
GetScan(ctx context.Context, objPointer interface{}, sql string, args ...interface{}) error // See Core.GetScan.
|
||||
Union(unions ...*Model) *Model // See Core.Union.
|
||||
UnionAll(unions ...*Model) *Model // See Core.UnionAll.
|
||||
@ -316,7 +316,7 @@ const (
|
||||
ctxKeyInternalProducedSQL gctx.StrKey = `CtxKeyInternalProducedSQL`
|
||||
|
||||
// type:[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||
linkPattern = `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([\w\-]*)\?{0,1}(.*)`
|
||||
linkPattern = `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)`
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -230,7 +230,7 @@ func (c *Core) GetValue(ctx context.Context, sql string, args ...interface{}) (V
|
||||
}
|
||||
|
||||
// GetCount queries and returns the count from database.
|
||||
func (c *Core) GetCount(ctx context.Context, sql string, args ...interface{}) (int64, error) {
|
||||
func (c *Core) GetCount(ctx context.Context, sql string, args ...interface{}) (int, error) {
|
||||
// If the query fields do not contain function "COUNT",
|
||||
// it replaces the sql string and adds the "COUNT" function to the fields.
|
||||
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, sql) {
|
||||
@ -240,7 +240,7 @@ func (c *Core) GetCount(ctx context.Context, sql string, args ...interface{}) (i
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return value.Int64(), nil
|
||||
return value.Int(), nil
|
||||
}
|
||||
|
||||
// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement.
|
||||
|
||||
@ -308,7 +308,7 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
|
||||
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
|
||||
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) Count(where ...interface{}) (int64, error) {
|
||||
func (m *Model) Count(where ...interface{}) (int, error) {
|
||||
var ctx = m.GetCtx()
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).Count()
|
||||
@ -324,7 +324,7 @@ func (m *Model) Count(where ...interface{}) (int64, error) {
|
||||
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
record := all[0]
|
||||
if v, ok := record[internalData.FirstResultColumn]; ok {
|
||||
return v.Int64(), nil
|
||||
return v.Int(), nil
|
||||
}
|
||||
}
|
||||
return 0, gerror.NewCode(
|
||||
@ -336,7 +336,7 @@ func (m *Model) Count(where ...interface{}) (int64, error) {
|
||||
}
|
||||
|
||||
// CountColumn does "SELECT COUNT(x) FROM ..." statement for the model.
|
||||
func (m *Model) CountColumn(column string) (int64, error) {
|
||||
func (m *Model) CountColumn(column string) (int, error) {
|
||||
if len(column) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@ -208,5 +208,8 @@ func (j *Json) Dump() {
|
||||
}
|
||||
j.mu.RLock()
|
||||
defer j.mu.RUnlock()
|
||||
if j.p == nil {
|
||||
return
|
||||
}
|
||||
gutil.Dump(*j.p)
|
||||
}
|
||||
|
||||
@ -50,3 +50,11 @@ func (j *Json) Interfaces() []interface{} {
|
||||
}
|
||||
return j.Array()
|
||||
}
|
||||
|
||||
// String returns current Json object as string.
|
||||
func (j *Json) String() string {
|
||||
if j.IsNil() {
|
||||
return ""
|
||||
}
|
||||
return j.MustToJsonString()
|
||||
}
|
||||
|
||||
@ -376,7 +376,7 @@ func Test_Convert2(t *testing.T) {
|
||||
j := gjson.New(`{"name":"gf","time":"2019-06-12"}`)
|
||||
t.Assert(j.Interface().(g.Map)["name"], "gf")
|
||||
t.Assert(j.Get("name1").Map(), nil)
|
||||
t.AssertNE(j.GetJson("name1"), nil)
|
||||
t.Assert(j.GetJson("name1"), nil)
|
||||
t.Assert(j.GetJsons("name1"), nil)
|
||||
t.Assert(j.GetJsonMap("name1"), nil)
|
||||
t.Assert(j.Contains("name1"), false)
|
||||
|
||||
@ -59,8 +59,12 @@ func BuildParams(params interface{}, noUrlEncode ...bool) (encodedParamStr strin
|
||||
encodedParamStr += "&"
|
||||
}
|
||||
s = gconv.String(v)
|
||||
if urlEncode && len(s) > 6 && strings.Compare(s[0:6], fileUploadingKey) != 0 {
|
||||
s = gurl.Encode(s)
|
||||
if urlEncode {
|
||||
if strings.HasPrefix(s, fileUploadingKey) && len(s) > len(fileUploadingKey) {
|
||||
// No url encoding if uploading file.
|
||||
} else {
|
||||
s = gurl.Encode(s)
|
||||
}
|
||||
}
|
||||
encodedParamStr += k + "=" + s
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func TestRwmutexIsSafe(t *testing.T) {
|
||||
func TestRWMutexIsSafe(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
lock := rwmutex.New()
|
||||
t.Assert(lock.IsSafe(), false)
|
||||
@ -37,103 +37,107 @@ func TestRwmutexIsSafe(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSafeRwmutex(t *testing.T) {
|
||||
func TestSafeRWMutex(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
safeLock := rwmutex.New(true)
|
||||
array := garray.New(true)
|
||||
var (
|
||||
localSafeLock = rwmutex.New(true)
|
||||
array = garray.New(true)
|
||||
)
|
||||
|
||||
go func() {
|
||||
safeLock.Lock()
|
||||
localSafeLock.Lock()
|
||||
array.Append(1)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
safeLock.Unlock()
|
||||
localSafeLock.Unlock()
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
safeLock.Lock()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
localSafeLock.Lock()
|
||||
array.Append(1)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
safeLock.Unlock()
|
||||
localSafeLock.Unlock()
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
t.Assert(array.Len(), 1)
|
||||
time.Sleep(80 * time.Millisecond)
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
t.Assert(array.Len(), 3)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 3)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 4)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSafeReaderRwmutex(t *testing.T) {
|
||||
func TestSafeReaderRWMutex(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
safeLock := rwmutex.New(true)
|
||||
array := garray.New(true)
|
||||
|
||||
var (
|
||||
localSafeLock = rwmutex.New(true)
|
||||
array = garray.New(true)
|
||||
)
|
||||
go func() {
|
||||
safeLock.RLock()
|
||||
localSafeLock.RLock()
|
||||
array.Append(1)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
localSafeLock.RUnlock()
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
localSafeLock.RLock()
|
||||
array.Append(1)
|
||||
safeLock.RUnlock()
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
localSafeLock.RUnlock()
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
safeLock.RLock()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
localSafeLock.Lock()
|
||||
array.Append(1)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
array.Append(1)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
array.Append(1)
|
||||
safeLock.RUnlock()
|
||||
localSafeLock.Unlock()
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
safeLock.Lock()
|
||||
array.Append(1)
|
||||
safeLock.Unlock()
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 3)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 4)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 6)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnsafeRwmutex(t *testing.T) {
|
||||
func TestUnsafeRWMutex(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
unsafeLock := rwmutex.New()
|
||||
array := garray.New(true)
|
||||
|
||||
var (
|
||||
localUnsafeLock = rwmutex.New()
|
||||
array = garray.New(true)
|
||||
)
|
||||
go func() {
|
||||
unsafeLock.Lock()
|
||||
localUnsafeLock.Lock()
|
||||
array.Append(1)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
unsafeLock.Unlock()
|
||||
localUnsafeLock.Unlock()
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
unsafeLock.Lock()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
localUnsafeLock.Lock()
|
||||
array.Append(1)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
array.Append(1)
|
||||
unsafeLock.Unlock()
|
||||
localUnsafeLock.Unlock()
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
t.Assert(array.Len(), 3)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
t.Assert(array.Len(), 3)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
t.Assert(array.Len(), 4)
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,8 +10,10 @@ package ghttp
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/net/gtrace"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
@ -142,6 +144,18 @@ func (r *Response) ClearBuffer() {
|
||||
r.buffer.Reset()
|
||||
}
|
||||
|
||||
// ServeContent replies to the request using the content in the
|
||||
// provided ReadSeeker. The main benefit of ServeContent over io.Copy
|
||||
// is that it handles Range requests properly, sets the MIME type, and
|
||||
// handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since,
|
||||
// and If-Range requests.
|
||||
//
|
||||
// See http.ServeContent
|
||||
func (r *Response) ServeContent(name string, modTime time.Time, content io.ReadSeeker) {
|
||||
r.wroteHeader = true
|
||||
http.ServeContent(r.Writer.RawWriter(), r.Request.Request, name, modTime, content)
|
||||
}
|
||||
|
||||
// Flush outputs the buffer content to the client and clears the buffer.
|
||||
func (r *Response) Flush() {
|
||||
r.Header().Set(responseHeaderTraceID, gtrace.GetTraceID(r.Request.Context()))
|
||||
|
||||
@ -16,10 +16,11 @@ import (
|
||||
|
||||
// ResponseWriter is the custom writer for http response.
|
||||
type ResponseWriter struct {
|
||||
Status int // HTTP status.
|
||||
writer http.ResponseWriter // The underlying ResponseWriter.
|
||||
buffer *bytes.Buffer // The output buffer.
|
||||
hijacked bool // Mark this request is hijacked or not.
|
||||
Status int // HTTP status.
|
||||
writer http.ResponseWriter // The underlying ResponseWriter.
|
||||
buffer *bytes.Buffer // The output buffer.
|
||||
hijacked bool // Mark this request is hijacked or not.
|
||||
wroteHeader bool // Is header wrote or not, avoiding error: superfluous/multiple response.WriteHeader call.
|
||||
}
|
||||
|
||||
// RawWriter returns the underlying ResponseWriter.
|
||||
@ -54,7 +55,9 @@ func (w *ResponseWriter) Flush() {
|
||||
if w.hijacked {
|
||||
return
|
||||
}
|
||||
|
||||
if w.Status != 0 && !w.isHeaderWritten() {
|
||||
w.wroteHeader = true
|
||||
w.writer.WriteHeader(w.Status)
|
||||
}
|
||||
// Default status text output.
|
||||
@ -69,6 +72,9 @@ func (w *ResponseWriter) Flush() {
|
||||
|
||||
// isHeaderWrote checks and returns whether the header is written.
|
||||
func (w *ResponseWriter) isHeaderWritten() bool {
|
||||
if w.wroteHeader {
|
||||
return true
|
||||
}
|
||||
if _, ok := w.writer.Header()[responseHeaderContentLength]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -20,8 +20,8 @@ import (
|
||||
|
||||
// staticPathItem is the item struct for static path configuration.
|
||||
type staticPathItem struct {
|
||||
prefix string // The router URI.
|
||||
path string // The static path.
|
||||
Prefix string // The router URI.
|
||||
Path string // The static path.
|
||||
}
|
||||
|
||||
// SetIndexFiles sets the index files for server.
|
||||
@ -96,8 +96,8 @@ func (s *Server) AddStaticPath(prefix string, path string) {
|
||||
}
|
||||
}
|
||||
addItem := staticPathItem{
|
||||
prefix: prefix,
|
||||
path: realPath,
|
||||
Prefix: prefix,
|
||||
Path: realPath,
|
||||
}
|
||||
if len(s.config.StaticPaths) > 0 {
|
||||
s.config.StaticPaths = append(s.config.StaticPaths, addItem)
|
||||
@ -112,13 +112,13 @@ func (s *Server) AddStaticPath(prefix string, path string) {
|
||||
return r
|
||||
})
|
||||
for _, v := range s.config.StaticPaths {
|
||||
array.Add(v.prefix)
|
||||
array.Add(v.Prefix)
|
||||
}
|
||||
// Add the items to paths by previous sorted slice.
|
||||
paths := make([]staticPathItem, 0)
|
||||
for _, v := range array.Slice() {
|
||||
for _, item := range s.config.StaticPaths {
|
||||
if strings.EqualFold(gconv.String(v), item.prefix) {
|
||||
if strings.EqualFold(gconv.String(v), item.Prefix) {
|
||||
paths = append(paths, item)
|
||||
break
|
||||
}
|
||||
|
||||
@ -211,19 +211,19 @@ func (s *Server) searchStaticFile(uri string) *staticFile {
|
||||
// Firstly search the StaticPaths mapping.
|
||||
if len(s.config.StaticPaths) > 0 {
|
||||
for _, item := range s.config.StaticPaths {
|
||||
if len(uri) >= len(item.prefix) && strings.EqualFold(item.prefix, uri[0:len(item.prefix)]) {
|
||||
if len(uri) >= len(item.Prefix) && strings.EqualFold(item.Prefix, uri[0:len(item.Prefix)]) {
|
||||
// To avoid case like: /static/style -> /static/style.css
|
||||
if len(uri) > len(item.prefix) && uri[len(item.prefix)] != '/' {
|
||||
if len(uri) > len(item.Prefix) && uri[len(item.Prefix)] != '/' {
|
||||
continue
|
||||
}
|
||||
file = gres.GetWithIndex(item.path+uri[len(item.prefix):], s.config.IndexFiles)
|
||||
file = gres.GetWithIndex(item.Path+uri[len(item.Prefix):], s.config.IndexFiles)
|
||||
if file != nil {
|
||||
return &staticFile{
|
||||
File: file,
|
||||
IsDir: file.FileInfo().IsDir(),
|
||||
}
|
||||
}
|
||||
path, dir = gspath.Search(item.path, uri[len(item.prefix):], s.config.IndexFiles...)
|
||||
path, dir = gspath.Search(item.Path, uri[len(item.Prefix):], s.config.IndexFiles...)
|
||||
if path != "" {
|
||||
return &staticFile{
|
||||
Path: path,
|
||||
@ -276,7 +276,7 @@ func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) {
|
||||
}
|
||||
} else {
|
||||
info := f.File.FileInfo()
|
||||
http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), f.File)
|
||||
r.Response.ServeContent(info.Name(), info.ModTime(), f.File)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -300,7 +300,7 @@ func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
}
|
||||
} else {
|
||||
http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), file)
|
||||
r.Response.ServeContent(info.Name(), info.ModTime(), file)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -590,6 +590,17 @@ func Test_Middleware_CORSAndAuth(t *testing.T) {
|
||||
t.Assert(resp.Header["Access-Control-Max-Age"][0], "3628800")
|
||||
resp.Close()
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
t.Assert(client.SetHeader("Access-Control-Request-Headers", "GF,GoFrame").GetContent(ctx, "/"), "Not Found")
|
||||
t.Assert(client.SetHeader("Origin", "GoFrame").GetContent(ctx, "/"), "Not Found")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
t.Assert(client.SetHeader("Referer", "Referer").PostContent(ctx, "/"), "Not Found")
|
||||
})
|
||||
}
|
||||
|
||||
func MiddlewareScope1(r *ghttp.Request) {
|
||||
@ -697,3 +708,31 @@ func Test_Middleware_JsonBody(t *testing.T) {
|
||||
t.Assert(client.PutContent(ctx, "/", `{"name":}`), "the request body content should be JSON format")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MiddlewareHandlerResponse(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(ghttp.MiddlewareHandlerResponse)
|
||||
group.GET("/403", func(r *ghttp.Request) {
|
||||
r.Response.WriteStatus(http.StatusForbidden, "")
|
||||
})
|
||||
group.GET("/default", func(r *ghttp.Request) {
|
||||
r.Response.WriteStatus(http.StatusInternalServerError, "")
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
rsp, err := client.Get(ctx, "/403")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.StatusCode, http.StatusForbidden)
|
||||
rsp, err = client.Get(ctx, "/default")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.StatusCode, http.StatusInternalServerError)
|
||||
})
|
||||
}
|
||||
|
||||
@ -141,3 +141,25 @@ func Test_Middleware_CORS2(t *testing.T) {
|
||||
resp.Close()
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Middleware_CORS3(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(ghttp.MiddlewareCORS)
|
||||
group.GET("/user/list/{type}", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Get("type"))
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
client.SetHeader("Access-Control-Request-Method", "POST")
|
||||
resp, err := client.Get(ctx, "/api.v2/user/list/1")
|
||||
t.AssertNil(err)
|
||||
resp.Close()
|
||||
})
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ package ghttp_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gbase64"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -18,6 +20,198 @@ import (
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
func Test_Request_IsFileRequest(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.IsFileRequest())
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(c.GetContent(ctx, "/"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_IsAjaxRequest(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.IsAjaxRequest())
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(c.GetContent(ctx, "/"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_GetClientIp(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetClientIp())
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
c := g.Client()
|
||||
c.SetHeader("X-Forwarded-For", "192.168.0.1")
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(c.GetContent(ctx, "/"), "192.168.0.1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_GetUrl(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetUrl())
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
c := g.Client()
|
||||
c.SetHeader("X-Forwarded-Proto", "https")
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(c.GetContent(ctx, "/"), fmt.Sprintf("https://127.0.0.1:%d/", s.GetListenedPort()))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_GetReferer(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetReferer())
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
c := g.Client()
|
||||
c.SetHeader("Referer", "Referer")
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(c.GetContent(ctx, "/"), "Referer")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_GetServeHandler(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetServeHandler() != nil)
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
c := g.Client()
|
||||
c.SetHeader("Referer", "Referer")
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(c.GetContent(ctx, "/"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_BasicAuth(t *testing.T) {
|
||||
const (
|
||||
user = "root"
|
||||
pass = "123456"
|
||||
wrongPass = "12345"
|
||||
)
|
||||
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/auth1", func(r *ghttp.Request) {
|
||||
r.BasicAuth(user, pass, "tips")
|
||||
})
|
||||
group.ALL("/auth2", func(r *ghttp.Request) {
|
||||
r.BasicAuth(user, pass)
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
rsp, err := c.Get(ctx, "/auth1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.Header.Get("WWW-Authenticate"), "Basic realm=\"tips\"")
|
||||
t.Assert(rsp.StatusCode, http.StatusUnauthorized)
|
||||
|
||||
rsp, err = c.SetHeader("Authorization", user+pass).Get(ctx, "/auth1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.StatusCode, http.StatusForbidden)
|
||||
|
||||
rsp, err = c.SetHeader("Authorization", "Test "+user+pass).Get(ctx, "/auth1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.StatusCode, http.StatusForbidden)
|
||||
|
||||
rsp, err = c.SetHeader("Authorization", "Basic "+user+pass).Get(ctx, "/auth1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.StatusCode, http.StatusForbidden)
|
||||
|
||||
rsp, err = c.SetHeader("Authorization", "Basic "+gbase64.EncodeString(user+pass)).Get(ctx, "/auth1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.StatusCode, http.StatusForbidden)
|
||||
|
||||
rsp, err = c.SetHeader("Authorization", "Basic "+gbase64.EncodeString(user+":"+wrongPass)).Get(ctx, "/auth1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.StatusCode, http.StatusUnauthorized)
|
||||
|
||||
rsp, err = c.BasicAuth(user, pass).Get(ctx, "/auth1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.StatusCode, http.StatusOK)
|
||||
|
||||
rsp, err = c.Get(ctx, "/auth2")
|
||||
t.AssertNil(err)
|
||||
t.Assert(rsp.Header.Get("WWW-Authenticate"), "Basic realm=\"Need Login\"")
|
||||
t.Assert(rsp.StatusCode, http.StatusUnauthorized)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_SetCtx(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
@ -42,3 +236,111 @@ func Test_Request_SetCtx(t *testing.T) {
|
||||
t.Assert(c.GetContent(ctx, "/"), "1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_GetCtx(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
ctx := context.WithValue(r.GetCtx(), "test", 1)
|
||||
r.SetCtx(ctx)
|
||||
r.Middleware.Next()
|
||||
})
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Context().Value("test"))
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(c.GetContent(ctx, "/"), "1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_GetCtxVar(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
r.Middleware.Next()
|
||||
})
|
||||
group.GET("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetCtxVar("key", "val"))
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/"), "val")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Request_Form(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
type Default struct {
|
||||
D string
|
||||
}
|
||||
s := g.Server(guid.S())
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
r.SetForm("key", "val")
|
||||
r.Response.Write(r.GetForm("key"))
|
||||
})
|
||||
group.ALL("/useDef", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetForm("key", "defVal"))
|
||||
})
|
||||
group.ALL("/GetFormMap", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetFormMap(map[string]interface{}{"key": "val"}))
|
||||
})
|
||||
group.ALL("/GetFormMap1", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetFormMap(map[string]interface{}{"array": "val"}))
|
||||
})
|
||||
group.ALL("/GetFormMapStrVar", func(r *ghttp.Request) {
|
||||
if r.Get("a") != nil {
|
||||
r.Response.Write(r.GetFormMapStrVar()["a"])
|
||||
}
|
||||
})
|
||||
group.ALL("/GetFormStruct", func(r *ghttp.Request) {
|
||||
var user User
|
||||
if err := r.GetFormStruct(&user); err != nil {
|
||||
r.Response.Write(err.Error())
|
||||
} else {
|
||||
r.Response.Write(user.Name)
|
||||
}
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/"), "val")
|
||||
t.Assert(client.GetContent(ctx, "/useDef"), "defVal")
|
||||
t.Assert(client.PostContent(ctx, "/GetFormMap"), "{\"key\":\"val\"}")
|
||||
t.Assert(client.PostContent(ctx, "/GetFormMap", "array[]=1&array[]=2"), "{\"key\":\"val\"}")
|
||||
t.Assert(client.PostContent(ctx, "/GetFormMap1", "array[]=1&array[]=2"), "{\"array\":[\"1\",\"2\"]}")
|
||||
t.Assert(client.GetContent(ctx, "/GetFormMapStrVar", "a=1&b=2"), nil)
|
||||
t.Assert(client.PostContent(ctx, "/GetFormMapStrVar", "a=1&b=2"), `1`)
|
||||
t.Assert(client.PostContent(ctx, "/GetFormStruct", g.Map{
|
||||
"id": 1,
|
||||
"name": "john",
|
||||
}), "john")
|
||||
})
|
||||
}
|
||||
|
||||
@ -9,9 +9,12 @@ package ghttp_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
@ -171,7 +174,7 @@ func Test_Params_File_Batch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Strict_Route_File_Single(t *testing.T) {
|
||||
func Test_Params_Strict_Route_File_Single_Ptr_Attrr(t *testing.T) {
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"post" mime:"multipart/form-data"`
|
||||
File *ghttp.UploadFile `type:"file"`
|
||||
@ -217,6 +220,48 @@ func Test_Params_Strict_Route_File_Single(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Strict_Route_File_Single_Struct_Attr(t *testing.T) {
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"post" mime:"multipart/form-data"`
|
||||
File ghttp.UploadFile `type:"file"`
|
||||
}
|
||||
type Res struct{}
|
||||
|
||||
dstDirPath := gfile.Temp(gtime.TimestampNanoStr())
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/upload/single", func(ctx context.Context, req *Req) (res *Res, err error) {
|
||||
var (
|
||||
r = g.RequestFromCtx(ctx)
|
||||
file = req.File
|
||||
)
|
||||
name, err := file.Save(dstDirPath)
|
||||
if err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(name)
|
||||
return
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// normal name
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
srcPath := gtest.DataPath("upload", "file1.txt")
|
||||
dstPath := gfile.Join(dstDirPath, "file1.txt")
|
||||
content := client.PostContent(ctx, "/upload/single", g.Map{
|
||||
"file": "@file:" + srcPath,
|
||||
})
|
||||
t.AssertNE(content, "")
|
||||
t.AssertNE(content, "upload failed")
|
||||
t.Assert(content, "file1.txt")
|
||||
t.Assert(gfile.GetContents(dstPath), gfile.GetContents(srcPath))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_File_Upload_Required(t *testing.T) {
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"post" mime:"multipart/form-data"`
|
||||
@ -241,3 +286,132 @@ func Test_Params_File_Upload_Required(t *testing.T) {
|
||||
t.Assert(content, `{"code":51,"message":"upload file is required","data":null}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_File_MarshalJSON(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/upload/single", func(r *ghttp.Request) {
|
||||
file := r.GetUploadFile("file")
|
||||
if file == nil {
|
||||
r.Response.WriteExit("upload file cannot be empty")
|
||||
}
|
||||
|
||||
if bytes, err := json.Marshal(file); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
} else {
|
||||
r.Response.WriteExit(bytes)
|
||||
}
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// normal name
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
srcPath := gtest.DataPath("upload", "file1.txt")
|
||||
content := client.PostContent(ctx, "/upload/single", g.Map{
|
||||
"file": "@file:" + srcPath,
|
||||
})
|
||||
t.Assert(strings.Contains(content, "file1.txt"), true)
|
||||
})
|
||||
}
|
||||
|
||||
// Select only one file when batch uploading
|
||||
func Test_Params_Strict_Route_File_Batch_Up_One(t *testing.T) {
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"post" mime:"multipart/form-data"`
|
||||
Files ghttp.UploadFiles `type:"file"`
|
||||
}
|
||||
type Res struct{}
|
||||
|
||||
dstDirPath := gfile.Temp(gtime.TimestampNanoStr())
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/upload/batch", func(ctx context.Context, req *Req) (res *Res, err error) {
|
||||
var (
|
||||
r = g.RequestFromCtx(ctx)
|
||||
files = req.Files
|
||||
)
|
||||
if len(files) == 0 {
|
||||
r.Response.WriteExit("upload file cannot be empty")
|
||||
}
|
||||
names, err := files.Save(dstDirPath)
|
||||
if err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(gstr.Join(names, ","))
|
||||
return
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// normal name
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
srcPath := gtest.DataPath("upload", "file1.txt")
|
||||
dstPath := gfile.Join(dstDirPath, "file1.txt")
|
||||
content := client.PostContent(ctx, "/upload/batch", g.Map{
|
||||
"files": "@file:" + srcPath,
|
||||
})
|
||||
t.AssertNE(content, "")
|
||||
t.AssertNE(content, "upload file cannot be empty")
|
||||
t.AssertNE(content, "upload failed")
|
||||
t.Assert(content, "file1.txt")
|
||||
t.Assert(gfile.GetContents(dstPath), gfile.GetContents(srcPath))
|
||||
})
|
||||
}
|
||||
|
||||
// Select multiple files during batch upload
|
||||
func Test_Params_Strict_Route_File_Batch_Up_Multiple(t *testing.T) {
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"post" mime:"multipart/form-data"`
|
||||
Files ghttp.UploadFiles `type:"file"`
|
||||
}
|
||||
type Res struct{}
|
||||
|
||||
dstDirPath := gfile.Temp(gtime.TimestampNanoStr())
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/upload/batch", func(ctx context.Context, req *Req) (res *Res, err error) {
|
||||
var (
|
||||
r = g.RequestFromCtx(ctx)
|
||||
files = req.Files
|
||||
)
|
||||
if len(files) == 0 {
|
||||
r.Response.WriteExit("upload file cannot be empty")
|
||||
}
|
||||
names, err := files.Save(dstDirPath)
|
||||
if err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(gstr.Join(names, ","))
|
||||
return
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// normal name
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
srcPath1 := gtest.DataPath("upload", "file1.txt")
|
||||
srcPath2 := gtest.DataPath("upload", "file2.txt")
|
||||
dstPath1 := gfile.Join(dstDirPath, "file1.txt")
|
||||
dstPath2 := gfile.Join(dstDirPath, "file2.txt")
|
||||
content := client.PostContent(ctx, "/upload/batch",
|
||||
"files=@file:"+srcPath1+
|
||||
"&files=@file:"+srcPath2,
|
||||
)
|
||||
t.AssertNE(content, "")
|
||||
t.AssertNE(content, "upload file cannot be empty")
|
||||
t.AssertNE(content, "upload failed")
|
||||
t.Assert(content, "file1.txt,file2.txt")
|
||||
t.Assert(gfile.GetContents(dstPath1), gfile.GetContents(srcPath1))
|
||||
t.Assert(gfile.GetContents(dstPath2), gfile.GetContents(srcPath2))
|
||||
})
|
||||
}
|
||||
|
||||
@ -32,6 +32,11 @@ func Test_Params_Parse(t *testing.T) {
|
||||
}
|
||||
r.Response.WriteExit(user.Map["id"], user.Map["score"])
|
||||
})
|
||||
s.BindHandler("/parseErr", func(r *ghttp.Request) {
|
||||
var user User
|
||||
err := r.Parse(user)
|
||||
r.Response.WriteExit(err != nil)
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
@ -41,6 +46,7 @@ func Test_Params_Parse(t *testing.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
t.Assert(client.PostContent(ctx, "/parse", `{"id":1,"name":"john","map":{"id":1,"score":100}}`), `1100`)
|
||||
t.Assert(client.PostContent(ctx, "/parseErr", `{"id":1,"name":"john","map":{"id":1,"score":100}}`), true)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -439,6 +439,19 @@ func Test_Params_GetRequestMap(t *testing.T) {
|
||||
s.BindHandler("/map", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRequestMap())
|
||||
})
|
||||
s.BindHandler("/withKVMap", func(r *ghttp.Request) {
|
||||
m := r.GetRequestMap(map[string]interface{}{"id": 2})
|
||||
r.Response.Write(m["id"])
|
||||
})
|
||||
s.BindHandler("/paramsMapWithKVMap", func(r *ghttp.Request) {
|
||||
r.SetParam("name", "john")
|
||||
m := r.GetRequestMap(map[string]interface{}{"id": 2})
|
||||
r.Response.Write(m["id"])
|
||||
})
|
||||
s.BindHandler("/{name}.map", func(r *ghttp.Request) {
|
||||
m := r.GetRequestMap(map[string]interface{}{"id": 2})
|
||||
r.Response.Write(m["id"])
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
@ -456,6 +469,15 @@ func Test_Params_GetRequestMap(t *testing.T) {
|
||||
),
|
||||
`{"attach":"","returnmsg":"Success"}`,
|
||||
)
|
||||
|
||||
t.Assert(client.PostContent(ctx, "/john.map", "name=john"), 2)
|
||||
|
||||
t.Assert(client.PostContent(ctx, "/withKVMap", "name=john"), 2)
|
||||
|
||||
t.Assert(client.PostContent(ctx, "/paramsMapWithKVMap"), 2)
|
||||
|
||||
client.SetContentType("application/json")
|
||||
t.Assert(client.GetContent(ctx, "/withKVMap", "name=john"), 2)
|
||||
})
|
||||
}
|
||||
|
||||
@ -636,3 +658,206 @@ func Test_Params_Parse_EmbeddedWithAliasName2(t *testing.T) {
|
||||
t.Assert(client.GetContent(ctx, "/parse?cate=1&page=2&size=10"), `{"Type":"","CategoryId":1,"Page":2,"Size":10,"Sort":0,"UserId":0}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_GetParam(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetParam("key", "val"))
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.PostContent(ctx, "/"), "val")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_SetQuery(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/SetQuery", func(r *ghttp.Request) {
|
||||
r.SetQuery("a", 100)
|
||||
r.Response.Write(r.GetQuery("a"))
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/SetQuery"), "100")
|
||||
t.Assert(client.GetContent(ctx, "/SetQuery?a=1"), "100")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_GetQuery(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/GetQuery", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetQuery("a", 200))
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/GetQuery"), 200)
|
||||
t.Assert(client.SetContentType("application/json").GetContent(ctx, "/GetQuery", "a=100"), 100)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_GetQueryMap(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/GetQueryMap", func(r *ghttp.Request) {
|
||||
if m := r.GetQueryMap(); len(m) > 0 {
|
||||
r.Response.Write(m["name"])
|
||||
}
|
||||
})
|
||||
s.BindHandler("/GetQueryMapWithKVMap", func(r *ghttp.Request) {
|
||||
if m := r.GetQueryMap(map[string]interface{}{"id": 1}); len(m) > 0 {
|
||||
r.Response.Write(m["id"])
|
||||
}
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
client.SetContentType("application/json")
|
||||
t.Assert(client.GetContent(ctx, "/GetQueryMap", "id=1&name=john"), `john`)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
t.Assert(client.GetContent(ctx, "/GetQueryMapWithKVMap"), 1)
|
||||
t.Assert(client.GetContent(ctx, "/GetQueryMapWithKVMap", "name=john"), 1)
|
||||
t.Assert(client.GetContent(ctx, "/GetQueryMapWithKVMap", "id=2&name=john"), 2)
|
||||
client.SetContentType("application/json")
|
||||
t.Assert(client.GetContent(ctx, "/GetQueryMapWithKVMap", "name=john"), 1)
|
||||
t.Assert(client.GetContent(ctx, "/GetQueryMapWithKVMap", "id=2&name=john"), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_GetQueryMapStrStr(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/GetQueryMapStrStr", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetQueryMapStrStr())
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/GetQueryMapStrStr"), "")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_GetQueryMapStrVar(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/GetQueryMapStrVar", func(r *ghttp.Request) {
|
||||
m := r.GetQueryMapStrVar()
|
||||
r.Response.Write(m["id"])
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/GetQueryMapStrVar"), "")
|
||||
t.Assert(client.GetContent(ctx, "/GetQueryMapStrVar", "id=1"), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_GetRequest(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/GetRequest", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRequest("id"))
|
||||
})
|
||||
s.BindHandler("/GetRequestWithDef", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRequest("id", 2))
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/GetRequestWithDef"), 2)
|
||||
|
||||
client.SetContentType("application/json")
|
||||
t.Assert(client.GetContent(ctx, "/GetRequest", "id=1"), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_GetRequestMapStrStr(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/GetRequestMapStrStr", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRequestMapStrStr())
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/GetRequestMapStrStr"), "")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_GetRequestMapStrVar(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/GetRequestMapStrVar", func(r *ghttp.Request) {
|
||||
m := r.GetRequestMapStrVar()
|
||||
r.Response.Write(m["id"])
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/GetRequestMapStrVar"), "")
|
||||
t.Assert(client.GetContent(ctx, "/GetRequestMapStrVar", "id=1"), 1)
|
||||
})
|
||||
}
|
||||
|
||||
309
net/ghttp/ghttp_z_unit_feature_response_test.go
Normal file
309
net/ghttp/ghttp_z_unit_feature_response_test.go
Normal file
@ -0,0 +1,309 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gxml"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/os/gview"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
func Test_Response_ServeFile(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/ServeFile", func(r *ghttp.Request) {
|
||||
filePath := r.GetQuery("filePath")
|
||||
r.Response.ServeFile(filePath.String())
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
srcPath := gtest.DataPath("upload", "file1.txt")
|
||||
t.Assert(client.GetContent(ctx, "/ServeFile", "filePath=file1.txt"), "Not Found")
|
||||
|
||||
t.Assert(
|
||||
client.GetContent(ctx, "/ServeFile", "filePath="+srcPath),
|
||||
"file1.txt: This file is for uploading unit test case.")
|
||||
|
||||
t.Assert(
|
||||
strings.Contains(
|
||||
client.GetContent(ctx, "/ServeFile", "filePath=files/server.key"),
|
||||
"BEGIN RSA PRIVATE KEY"),
|
||||
true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Response_ServeFileDownload(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/ServeFileDownload", func(r *ghttp.Request) {
|
||||
filePath := r.GetQuery("filePath")
|
||||
r.Response.ServeFileDownload(filePath.String())
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
srcPath := gtest.DataPath("upload", "file1.txt")
|
||||
t.Assert(client.GetContent(ctx, "/ServeFileDownload", "filePath=file1.txt"), "Not Found")
|
||||
|
||||
t.Assert(
|
||||
client.GetContent(ctx, "/ServeFileDownload", "filePath="+srcPath),
|
||||
"file1.txt: This file is for uploading unit test case.")
|
||||
|
||||
t.Assert(
|
||||
strings.Contains(
|
||||
client.GetContent(ctx, "/ServeFileDownload", "filePath=files/server.key"),
|
||||
"BEGIN RSA PRIVATE KEY"),
|
||||
true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Response_Redirect(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("RedirectResult")
|
||||
})
|
||||
s.BindHandler("/RedirectTo", func(r *ghttp.Request) {
|
||||
r.Response.RedirectTo("/")
|
||||
})
|
||||
s.BindHandler("/RedirectTo301", func(r *ghttp.Request) {
|
||||
r.Response.RedirectTo("/", http.StatusMovedPermanently)
|
||||
})
|
||||
s.BindHandler("/RedirectBack", func(r *ghttp.Request) {
|
||||
r.Response.RedirectBack()
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/RedirectTo"), "RedirectResult")
|
||||
t.Assert(client.GetContent(ctx, "/RedirectTo301"), "RedirectResult")
|
||||
t.Assert(client.SetHeader("Referer", "/").GetContent(ctx, "/RedirectBack"), "RedirectResult")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Response_Buffer(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/Buffer", func(r *ghttp.Request) {
|
||||
name := r.GetQuery("name").Bytes()
|
||||
r.Response.SetBuffer(name)
|
||||
buffer := r.Response.Buffer()
|
||||
r.Response.ClearBuffer()
|
||||
r.Response.Write(buffer)
|
||||
})
|
||||
s.BindHandler("/BufferString", func(r *ghttp.Request) {
|
||||
name := r.GetQuery("name").Bytes()
|
||||
r.Response.SetBuffer(name)
|
||||
bufferString := r.Response.BufferString()
|
||||
r.Response.ClearBuffer()
|
||||
r.Response.Write(bufferString)
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/Buffer", "name=john"), []byte("john"))
|
||||
t.Assert(client.GetContent(ctx, "/BufferString", "name=john"), "john")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Response_WriteTpl(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New(gtest.DataPath("template", "basic"))
|
||||
s := g.Server(guid.S())
|
||||
s.SetView(v)
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
err := r.Response.WriteTpl("noexist.html", g.Map{
|
||||
"name": "john",
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.AssertNE(client.GetContent(ctx, "/"), "Name:john")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Response_WriteTplDefault(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
v.SetDefaultFile(gtest.DataPath("template", "basic", "index.html"))
|
||||
s := g.Server(guid.S())
|
||||
s.SetView(v)
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
err := r.Response.WriteTplDefault(g.Map{"name": "john"})
|
||||
t.AssertNil(err)
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/"), "Name:john")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
v.SetDefaultFile(gtest.DataPath("template", "basic", "noexit.html"))
|
||||
s := g.Server(guid.S())
|
||||
s.SetView(v)
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
err := r.Response.WriteTplDefault(g.Map{"name": "john"})
|
||||
t.AssertNil(err)
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.AssertNE(client.GetContent(ctx, "/"), "Name:john")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Response_ParseTplDefault(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
v.SetDefaultFile(gtest.DataPath("template", "basic", "index.html"))
|
||||
s := g.Server(guid.S())
|
||||
s.SetView(v)
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
res, err := r.Response.ParseTplDefault(g.Map{"name": "john"})
|
||||
t.AssertNil(err)
|
||||
r.Response.Write(res)
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/"), "Name:john")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Response_Write(t *testing.T) {
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write()
|
||||
})
|
||||
s.BindHandler("/WriteOverExit", func(r *ghttp.Request) {
|
||||
r.Response.Write("WriteOverExit")
|
||||
r.Response.WriteOverExit("")
|
||||
})
|
||||
s.BindHandler("/WritefExit", func(r *ghttp.Request) {
|
||||
r.Response.WritefExit("%s", "WritefExit")
|
||||
})
|
||||
s.BindHandler("/Writeln", func(r *ghttp.Request) {
|
||||
name := r.GetQuery("name")
|
||||
r.Response.Writeln(name)
|
||||
})
|
||||
s.BindHandler("/WritelnNil", func(r *ghttp.Request) {
|
||||
r.Response.Writeln()
|
||||
})
|
||||
s.BindHandler("/Writefln", func(r *ghttp.Request) {
|
||||
name := r.GetQuery("name")
|
||||
r.Response.Writefln("%s", name)
|
||||
})
|
||||
s.BindHandler("/WriteJson", func(r *ghttp.Request) {
|
||||
m := map[string]string{"name": "john"}
|
||||
if bytes, err := json.Marshal(m); err == nil {
|
||||
r.Response.WriteJson(bytes)
|
||||
}
|
||||
})
|
||||
s.BindHandler("/WriteJsonP", func(r *ghttp.Request) {
|
||||
m := map[string]string{"name": "john"}
|
||||
if bytes, err := json.Marshal(m); err == nil {
|
||||
r.Response.WriteJsonP(bytes)
|
||||
}
|
||||
})
|
||||
s.BindHandler("/WriteJsonPWithStruct", func(r *ghttp.Request) {
|
||||
user := User{"john"}
|
||||
r.Response.WriteJsonP(user)
|
||||
})
|
||||
s.BindHandler("/WriteXml", func(r *ghttp.Request) {
|
||||
m := map[string]interface{}{"name": "john"}
|
||||
if bytes, err := gxml.Encode(m); err == nil {
|
||||
r.Response.WriteXml(bytes)
|
||||
}
|
||||
})
|
||||
s.BindHandler("/WriteXmlWithStruct", func(r *ghttp.Request) {
|
||||
user := User{"john"}
|
||||
r.Response.WriteXml(user)
|
||||
})
|
||||
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/"), "")
|
||||
t.Assert(client.GetContent(ctx, "/WriteOverExit"), "")
|
||||
t.Assert(client.GetContent(ctx, "/WritefExit"), "WritefExit")
|
||||
t.Assert(client.GetContent(ctx, "/Writeln"), "\n")
|
||||
t.Assert(client.GetContent(ctx, "/WritelnNil"), "\n")
|
||||
t.Assert(client.GetContent(ctx, "/Writeln", "name=john"), "john\n")
|
||||
t.Assert(client.GetContent(ctx, "/Writefln", "name=john"), "john\n")
|
||||
t.Assert(client.GetContent(ctx, "/WriteJson"), "{\"name\":\"john\"}")
|
||||
t.Assert(client.GetContent(ctx, "/WriteJsonP"), "{\"name\":\"john\"}")
|
||||
t.Assert(client.GetContent(ctx, "/WriteJsonPWithStruct"), "{\"name\":\"john\"}")
|
||||
t.Assert(client.GetContent(ctx, "/WriteJsonPWithStruct", "callback=callback"),
|
||||
"callback({\"name\":\"john\"})")
|
||||
t.Assert(client.GetContent(ctx, "/WriteXml"), "<name>john</name>")
|
||||
t.Assert(client.GetContent(ctx, "/WriteXmlWithStruct"), "<name>john</name>")
|
||||
})
|
||||
}
|
||||
@ -72,6 +72,12 @@ func Test_Router_Basic2(t *testing.T) {
|
||||
|
||||
func Test_Router_Value(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRouterMap()["hash"])
|
||||
})
|
||||
s.BindHandler("/GetRouter", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRouter("name", "john").String())
|
||||
})
|
||||
s.BindHandler("/{hash}", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRouter("hash").String())
|
||||
})
|
||||
@ -89,6 +95,8 @@ func Test_Router_Value(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
t.Assert(client.GetContent(ctx, "/"), "")
|
||||
t.Assert(client.GetContent(ctx, "/GetRouter"), "john")
|
||||
t.Assert(client.GetContent(ctx, "/data"), "data")
|
||||
t.Assert(client.GetContent(ctx, "/data.json"), "json")
|
||||
t.Assert(client.GetContent(ctx, "/data.json.map"), "json")
|
||||
|
||||
@ -131,7 +131,6 @@ func Test_Issue1653(t *testing.T) {
|
||||
s.Group("/boot", func(grp *ghttp.RouterGroup) {
|
||||
grp.Bind(Issue1653Foo)
|
||||
})
|
||||
s.SetPort(9527)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
@ -207,7 +206,6 @@ func Test_Issue662(t *testing.T) {
|
||||
s.Group("/boot", func(grp *ghttp.RouterGroup) {
|
||||
grp.Bind(Foo1)
|
||||
})
|
||||
s.SetPort(8888)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
@ -251,7 +249,6 @@ func Test_Issue2172(t *testing.T) {
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Bind(api)
|
||||
})
|
||||
s.SetPort(8888)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
@ -264,3 +261,22 @@ func Test_Issue2172(t *testing.T) {
|
||||
t.Assert(c.PostContent(ctx, "/demo", dataReq), `{"code":0,"message":"","data":{"Content":"{\"asd\":1}"}}`)
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2334
|
||||
func Test_Issue2334(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.SetServerRoot(gtest.DataPath("static1"))
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||
t.Assert(c.GetContent(ctx, "/index.html"), "index")
|
||||
|
||||
c.SetHeader("If-Modified-Since", "Mon, 12 Dec 2040 05:53:35 GMT")
|
||||
res, _ := c.Get(ctx, "/index.html")
|
||||
t.Assert(res.StatusCode, 304)
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,11 +10,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gurl"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/internal/httputil"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/genv"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
@ -139,3 +141,30 @@ func Test_RoutePathParams(t *testing.T) {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_BuildParams(t *testing.T) {
|
||||
// normal && special cases
|
||||
params := map[string]string{
|
||||
"val": "12345678",
|
||||
"code1": "x&a=1", // for fix
|
||||
"code2": "x&a=111",
|
||||
"id": "1+- ", // for fix
|
||||
"f": "1#a=+- ",
|
||||
"v": "",
|
||||
"n": "null",
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
res1 := httputil.BuildParams(params)
|
||||
vs, _ := url.ParseQuery(res1)
|
||||
t.Assert(len(params), len(vs))
|
||||
for k := range vs {
|
||||
vv := vs.Get(k)
|
||||
_, ok := params[k]
|
||||
// check no additional param
|
||||
t.Assert(ok, true)
|
||||
// check equal
|
||||
t.AssertEQ(params[k], vv)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -96,6 +96,7 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
|
||||
Responses: map[string]ResponseRef{},
|
||||
XExtensions: make(XExtensions),
|
||||
}
|
||||
seRequirement = SecurityRequirement{}
|
||||
)
|
||||
// Path check.
|
||||
if in.Path == "" {
|
||||
@ -145,6 +146,18 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
|
||||
}
|
||||
}
|
||||
|
||||
// path security
|
||||
// note: the security schema type only support http and apiKey;not support oauth2 and openIdConnect.
|
||||
// multi schema separate with comma, e.g. `security: apiKey1,apiKey2`
|
||||
TagNameSecurity := gmeta.Get(inputObject.Interface(), gtag.Security).String()
|
||||
securities := gstr.SplitAndTrim(TagNameSecurity, ",")
|
||||
for _, sec := range securities {
|
||||
seRequirement[sec] = []string{}
|
||||
}
|
||||
if len(securities) > 0 {
|
||||
operation.Security = &SecurityRequirements{seRequirement}
|
||||
}
|
||||
|
||||
// =================================================================================================================
|
||||
// Request Parameter.
|
||||
// =================================================================================================================
|
||||
|
||||
@ -1046,3 +1046,56 @@ func Test_NameFromJsonTag(t *testing.T) {
|
||||
t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateReq":{"properties":{"nick_name":{"format":"string","properties":{},"type":"string"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_PathSecurity(t *testing.T) {
|
||||
type CommonResponse struct {
|
||||
Code int `json:"code" description:"Error code"`
|
||||
Message string `json:"message" description:"Error message"`
|
||||
Data interface{} `json:"data" description:"Result data for certain request according API definition"`
|
||||
}
|
||||
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"PUT" security:"apiKey"` // 这里的apiKey要和openApi定义的key一致
|
||||
Product string `json:"product" v:"required" description:"Unique product key"`
|
||||
Name string `json:"name" v:"required" description:"Instance name"`
|
||||
}
|
||||
type Res struct{}
|
||||
|
||||
f := func(ctx context.Context, req *Req) (res *Res, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
)
|
||||
|
||||
oai.Config.CommonResponse = CommonResponse{}
|
||||
oai.Components = goai.Components{
|
||||
SecuritySchemes: goai.SecuritySchemes{
|
||||
"apiKey": goai.SecuritySchemeRef{
|
||||
Ref: "",
|
||||
Value: &goai.SecurityScheme{
|
||||
// 此处type是openApi的规定,详见 https://swagger.io/docs/specification/authentication/api-keys/
|
||||
Type: "apiKey",
|
||||
In: "header",
|
||||
Name: "X-API-KEY",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/index",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
fmt.Println(oai.String())
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 3)
|
||||
t.Assert(len(oai.Components.SecuritySchemes), 1)
|
||||
t.Assert(oai.Components.SecuritySchemes["apiKey"].Value.Type, "apiKey")
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 3)
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ package gcache_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -60,6 +61,10 @@ func Test_AdapterRedis_Basic1(t *testing.T) {
|
||||
n, _ := cacheRedis.Size(ctx)
|
||||
t.Assert(n, 0)
|
||||
})
|
||||
// Close
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.AssertNil(cacheRedis.Close(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_Basic2(t *testing.T) {
|
||||
@ -152,6 +157,37 @@ func Test_AdapterRedis_UpdateExpire(t *testing.T) {
|
||||
t.Assert(d > time.Second, true)
|
||||
t.Assert(d <= 2*time.Second, true)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
key = "key"
|
||||
value = "value"
|
||||
)
|
||||
t.AssertNil(cacheRedis.Set(ctx, key, value, time.Second))
|
||||
v, _ := cacheRedis.Get(ctx, key)
|
||||
t.Assert(v, value)
|
||||
|
||||
_, err := cacheRedis.UpdateExpire(ctx, key, -1)
|
||||
t.AssertNil(err)
|
||||
v, _ = cacheRedis.Get(ctx, key)
|
||||
t.AssertNil(v)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
key = "key"
|
||||
value = "value"
|
||||
)
|
||||
|
||||
t.AssertNil(cacheRedis.Set(ctx, key, value, time.Second))
|
||||
v, _ := cacheRedis.Get(ctx, key)
|
||||
t.Assert(v, value)
|
||||
|
||||
_, err := cacheRedis.UpdateExpire(ctx, key, 0)
|
||||
t.AssertNil(err)
|
||||
v, _ = cacheRedis.Get(ctx, key)
|
||||
t.Assert(v, value)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_SetIfNotExist(t *testing.T) {
|
||||
@ -176,6 +212,69 @@ func Test_AdapterRedis_SetIfNotExist(t *testing.T) {
|
||||
d, _ := cacheRedis.GetExpire(ctx, key)
|
||||
t.Assert(d > time.Millisecond*500, true)
|
||||
t.Assert(d <= time.Second, true)
|
||||
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
key = "key"
|
||||
value1 = "value1"
|
||||
key2 = "key2"
|
||||
value2 = "value2"
|
||||
)
|
||||
t.AssertNil(cacheRedis.Set(ctx, key, value1, time.Second))
|
||||
v, _ := cacheRedis.Get(ctx, key)
|
||||
t.Assert(v, value1)
|
||||
|
||||
r, _ := cacheRedis.SetIfNotExist(ctx, key, value1, -1)
|
||||
t.Assert(r, true)
|
||||
v, _ = cacheRedis.Get(ctx, key)
|
||||
t.AssertNil(v)
|
||||
|
||||
r, _ = cacheRedis.SetIfNotExist(ctx, key, value2, -1)
|
||||
t.Assert(r, false)
|
||||
|
||||
r, _ = cacheRedis.SetIfNotExist(ctx, key2, value2, time.Second)
|
||||
t.Assert(r, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_SetIfNotExistFunc(t *testing.T) {
|
||||
defer cacheRedis.Clear(ctx)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
exist, err := cacheRedis.SetIfNotExistFunc(ctx, 1, func(ctx context.Context) (value interface{}, err error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.AssertNil(err)
|
||||
t.Assert(exist, false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_SetIfNotExistFuncLock(t *testing.T) {
|
||||
defer cacheRedis.Clear(ctx)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
exist, err := cacheRedis.SetIfNotExistFuncLock(ctx, 1, func(ctx context.Context) (value interface{}, err error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.AssertNil(err)
|
||||
t.Assert(exist, false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_GetOrSet(t *testing.T) {
|
||||
defer cacheRedis.Clear(ctx)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
key = "key"
|
||||
value1 = "valueFunc"
|
||||
)
|
||||
v, err := cacheRedis.GetOrSet(ctx, key, value1, 0)
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, value1)
|
||||
|
||||
v, err = cacheRedis.GetOrSet(ctx, key, value1, 0)
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, value1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -192,6 +291,24 @@ func Test_AdapterRedis_GetOrSetFunc(t *testing.T) {
|
||||
}, 0)
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, value1)
|
||||
|
||||
v, err = cacheRedis.GetOrSetFunc(ctx, key, func(ctx context.Context) (value interface{}, err error) {
|
||||
value = value1
|
||||
return
|
||||
}, 0)
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, value1)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
key = "key1"
|
||||
)
|
||||
v, err := cacheRedis.GetOrSetFunc(ctx, key, func(ctx context.Context) (interface{}, error) {
|
||||
return nil, nil
|
||||
}, 0)
|
||||
t.AssertNil(err)
|
||||
t.AssertNil(v)
|
||||
})
|
||||
}
|
||||
|
||||
@ -210,3 +327,86 @@ func Test_AdapterRedis_GetOrSetFuncLock(t *testing.T) {
|
||||
t.Assert(v, value1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_SetMap(t *testing.T) {
|
||||
defer cacheRedis.Clear(ctx)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.AssertNil(cacheRedis.SetMap(ctx, g.MapAnyAny{}, 0))
|
||||
|
||||
t.AssertNil(cacheRedis.SetMap(ctx, g.MapAnyAny{1: 11, 2: 22}, 0))
|
||||
v, _ := cacheRedis.Get(ctx, 1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
t.AssertNil(cacheRedis.SetMap(ctx, g.MapAnyAny{1: 11, 2: 22}, -1))
|
||||
v, _ = cacheRedis.Get(ctx, 1)
|
||||
t.AssertNil(v)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_Contains(t *testing.T) {
|
||||
defer cacheRedis.Clear(ctx)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.AssertNil(cacheRedis.Set(ctx, "key", "value", 0))
|
||||
|
||||
result, err := cacheRedis.Contains(ctx, "key")
|
||||
t.AssertNil(err)
|
||||
t.Assert(result, true)
|
||||
|
||||
result, err = cacheRedis.Contains(ctx, "key1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(result, false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_Keys(t *testing.T) {
|
||||
defer cacheRedis.Clear(ctx)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.AssertNil(cacheRedis.Set(ctx, "key1", "value1", 0))
|
||||
|
||||
keys, err := cacheRedis.Keys(ctx)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(keys), 1)
|
||||
|
||||
t.AssertNil(cacheRedis.Set(ctx, "key2", "value2", 0))
|
||||
|
||||
keys, err = cacheRedis.Keys(ctx)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(keys), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_Values(t *testing.T) {
|
||||
defer cacheRedis.Clear(ctx)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.AssertNil(cacheRedis.Set(ctx, "key1", "value1", 0))
|
||||
|
||||
values, err := cacheRedis.Values(ctx)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(values), 1)
|
||||
|
||||
t.AssertNil(cacheRedis.Set(ctx, "key2", "value2", 0))
|
||||
|
||||
values, err = cacheRedis.Values(ctx)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(values), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AdapterRedis_Remove(t *testing.T) {
|
||||
defer cacheRedis.Clear(ctx)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
key = "key"
|
||||
value = "value"
|
||||
)
|
||||
val, err := cacheRedis.Remove(ctx)
|
||||
t.AssertNil(val)
|
||||
t.AssertNil(err)
|
||||
|
||||
t.AssertNil(cacheRedis.Set(ctx, key, value, 0))
|
||||
|
||||
val, err = cacheRedis.Remove(ctx, key)
|
||||
t.Assert(val, value)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -569,3 +569,56 @@ func TestCache_Removes(t *testing.T) {
|
||||
t.Assert(ok, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Basic_Must(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
defer gcache.Remove(ctx, g.Slice{1, 2, 3, 4}...)
|
||||
|
||||
t.AssertNil(gcache.Set(ctx, 1, 11, 0))
|
||||
v := gcache.MustGet(ctx, 1)
|
||||
t.Assert(v, 11)
|
||||
gcache.MustGetOrSet(ctx, 2, 22, 0)
|
||||
v = gcache.MustGet(ctx, 2)
|
||||
t.Assert(v, 22)
|
||||
|
||||
gcache.MustGetOrSetFunc(ctx, 3, func(ctx context.Context) (value interface{}, err error) {
|
||||
return 33, nil
|
||||
}, 0)
|
||||
v = gcache.MustGet(ctx, 3)
|
||||
t.Assert(v, 33)
|
||||
|
||||
gcache.GetOrSetFuncLock(ctx, 4, func(ctx context.Context) (value interface{}, err error) {
|
||||
return 44, nil
|
||||
}, 0)
|
||||
v = gcache.MustGet(ctx, 4)
|
||||
t.Assert(v, 44)
|
||||
|
||||
t.Assert(gcache.MustContains(ctx, 1), true)
|
||||
|
||||
t.AssertNil(gcache.Set(ctx, 1, 11, 3*time.Second))
|
||||
expire := gcache.MustGetExpire(ctx, 1)
|
||||
t.AssertGE(expire, 0)
|
||||
|
||||
n := gcache.MustSize(ctx)
|
||||
t.Assert(n, 4)
|
||||
|
||||
data := gcache.MustData(ctx)
|
||||
t.Assert(len(data), 4)
|
||||
|
||||
keys := gcache.MustKeys(ctx)
|
||||
t.Assert(len(keys), 4)
|
||||
|
||||
keyStrings := gcache.MustKeyStrings(ctx)
|
||||
t.Assert(len(keyStrings), 4)
|
||||
|
||||
values := gcache.MustValues(ctx)
|
||||
t.Assert(len(values), 4)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_NewWithAdapter(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.NewWithAdapter(gcache.NewAdapterMemory())
|
||||
t.AssertNE(cache, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
@ -58,6 +59,15 @@ func Test_BuildOptions(t *testing.T) {
|
||||
}, "-test")
|
||||
t.Assert(s, "-testn=john")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gcmd.BuildOptions(g.MapStrStr{
|
||||
"n1": "john",
|
||||
"n2": "huang",
|
||||
})
|
||||
t.Assert(strings.Contains(s, "-n1=john"), true)
|
||||
t.Assert(strings.Contains(s, "-n2=huang"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_GetWithEnv(t *testing.T) {
|
||||
|
||||
@ -112,21 +112,16 @@ func TestCron_Add_FixedPattern(t *testing.T) {
|
||||
func doTestCronAddFixedPattern(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
now = time.Now()
|
||||
cron = gcron.New()
|
||||
array = garray.New(true)
|
||||
minutes = now.Minute()
|
||||
seconds = now.Second() + 2
|
||||
now = time.Now()
|
||||
cron = gcron.New()
|
||||
array = garray.New(true)
|
||||
expect = now.Add(time.Second * 2)
|
||||
)
|
||||
defer cron.Close()
|
||||
|
||||
if seconds >= 60 {
|
||||
seconds %= 60
|
||||
minutes++
|
||||
}
|
||||
var pattern = fmt.Sprintf(
|
||||
`%d %d %d %d %d %s`,
|
||||
seconds, minutes, now.Hour(), now.Day(), now.Month(), now.Weekday().String(),
|
||||
expect.Second(), expect.Minute(), expect.Hour(), expect.Day(), expect.Month(), expect.Weekday().String(),
|
||||
)
|
||||
cron.SetLogger(g.Log())
|
||||
g.Log().Debugf(ctx, `pattern: %s`, pattern)
|
||||
|
||||
@ -72,6 +72,15 @@ func Test_Create(t *testing.T) {
|
||||
t.AssertNil(err)
|
||||
}
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
tmpPath := gfile.Join(gfile.Temp(), "test/testfile_cc1.txt")
|
||||
fileobj, err := gfile.Create(tmpPath)
|
||||
defer gfile.Remove(tmpPath)
|
||||
t.AssertNE(fileobj, nil)
|
||||
t.AssertNil(err)
|
||||
fileobj.Close()
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Open(t *testing.T) {
|
||||
|
||||
@ -15,6 +15,13 @@ import (
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
)
|
||||
|
||||
type recursiveType string
|
||||
|
||||
const (
|
||||
recursiveTypeAuto recursiveType = "auto"
|
||||
recursiveTypeTrue recursiveType = "true"
|
||||
)
|
||||
|
||||
// Map converts any variable `value` to map[string]interface{}. If the parameter `value` is not a
|
||||
// map/struct/*struct type, then the conversion will fail and returns nil.
|
||||
//
|
||||
@ -22,7 +29,7 @@ import (
|
||||
// tags that will be detected, otherwise it detects the tags in order of:
|
||||
// gconv, json, field name.
|
||||
func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
return doMapConvert(value, false, tags...)
|
||||
return doMapConvert(value, recursiveTypeAuto, tags...)
|
||||
}
|
||||
|
||||
// MapDeep does Map function recursively, which means if the attribute of `value`
|
||||
@ -30,14 +37,14 @@ func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
// a map[string]interface{} type variable.
|
||||
// Also see Map.
|
||||
func MapDeep(value interface{}, tags ...string) map[string]interface{} {
|
||||
return doMapConvert(value, true, tags...)
|
||||
return doMapConvert(value, recursiveTypeTrue, tags...)
|
||||
}
|
||||
|
||||
// doMapConvert implements the map converting.
|
||||
// It automatically checks and converts json string to map if `value` is string/[]byte.
|
||||
//
|
||||
// TODO completely implement the recursive converting for all types, especially the map.
|
||||
func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]interface{} {
|
||||
func doMapConvert(value interface{}, recursive recursiveType, tags ...string) map[string]interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
@ -73,7 +80,15 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
}
|
||||
case map[interface{}]interface{}:
|
||||
for k, v := range r {
|
||||
dataMap[String(k)] = doMapConvertForMapOrStructValue(false, v, recursive, newTags...)
|
||||
dataMap[String(k)] = doMapConvertForMapOrStructValue(
|
||||
doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: v,
|
||||
RecursiveType: recursive,
|
||||
RecursiveOption: recursive == recursiveTypeTrue,
|
||||
Tags: newTags,
|
||||
},
|
||||
)
|
||||
}
|
||||
case map[interface{}]string:
|
||||
for k, v := range r {
|
||||
@ -120,10 +135,18 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]interface{}:
|
||||
if recursive {
|
||||
if recursive == recursiveTypeTrue {
|
||||
// A copy of current map.
|
||||
for k, v := range r {
|
||||
dataMap[k] = doMapConvertForMapOrStructValue(false, v, recursive, newTags...)
|
||||
dataMap[k] = doMapConvertForMapOrStructValue(
|
||||
doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: v,
|
||||
RecursiveType: recursive,
|
||||
RecursiveOption: recursive == recursiveTypeTrue,
|
||||
Tags: newTags,
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// It returns the map directly without any changing.
|
||||
@ -131,7 +154,15 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
}
|
||||
case map[int]interface{}:
|
||||
for k, v := range r {
|
||||
dataMap[String(k)] = doMapConvertForMapOrStructValue(false, v, recursive, newTags...)
|
||||
dataMap[String(k)] = doMapConvertForMapOrStructValue(
|
||||
doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: v,
|
||||
RecursiveType: recursive,
|
||||
RecursiveOption: recursive == recursiveTypeTrue,
|
||||
Tags: newTags,
|
||||
},
|
||||
)
|
||||
}
|
||||
case map[int]string:
|
||||
for k, v := range r {
|
||||
@ -171,7 +202,15 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
}
|
||||
}
|
||||
case reflect.Map, reflect.Struct, reflect.Interface:
|
||||
convertedValue := doMapConvertForMapOrStructValue(true, value, recursive, newTags...)
|
||||
convertedValue := doMapConvertForMapOrStructValue(
|
||||
doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: true,
|
||||
Value: value,
|
||||
RecursiveType: recursive,
|
||||
RecursiveOption: recursive == recursiveTypeTrue,
|
||||
Tags: newTags,
|
||||
},
|
||||
)
|
||||
if m, ok := convertedValue.(map[string]interface{}); ok {
|
||||
return m
|
||||
}
|
||||
@ -183,16 +222,25 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
return dataMap
|
||||
}
|
||||
|
||||
func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive bool, tags ...string) interface{} {
|
||||
if isRoot == false && recursive == false {
|
||||
return value
|
||||
type doMapConvertForMapOrStructValueInput struct {
|
||||
IsRoot bool // It returns directly if it is not root and with no recursive converting.
|
||||
Value interface{} // Current operation value.
|
||||
RecursiveType recursiveType // The type from top function entry.
|
||||
RecursiveOption bool // Whether convert recursively for `current` operation.
|
||||
Tags []string // Map key mapping.
|
||||
}
|
||||
|
||||
func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) interface{} {
|
||||
if in.IsRoot == false && in.RecursiveOption == false {
|
||||
return in.Value
|
||||
}
|
||||
|
||||
var reflectValue reflect.Value
|
||||
if v, ok := value.(reflect.Value); ok {
|
||||
if v, ok := in.Value.(reflect.Value); ok {
|
||||
reflectValue = v
|
||||
value = v.Interface()
|
||||
in.Value = v.Interface()
|
||||
} else {
|
||||
reflectValue = reflect.ValueOf(value)
|
||||
reflectValue = reflect.ValueOf(in.Value)
|
||||
}
|
||||
reflectKind := reflectValue.Kind()
|
||||
// If it is a pointer, we should find its real data type.
|
||||
@ -208,10 +256,13 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
)
|
||||
for _, k := range mapKeys {
|
||||
dataMap[String(k.Interface())] = doMapConvertForMapOrStructValue(
|
||||
false,
|
||||
reflectValue.MapIndex(k).Interface(),
|
||||
recursive,
|
||||
tags...,
|
||||
doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: reflectValue.MapIndex(k).Interface(),
|
||||
RecursiveType: in.RecursiveType,
|
||||
RecursiveOption: in.RecursiveType == recursiveTypeTrue,
|
||||
Tags: in.Tags,
|
||||
},
|
||||
)
|
||||
}
|
||||
return dataMap
|
||||
@ -219,11 +270,19 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
case reflect.Struct:
|
||||
var dataMap = make(map[string]interface{})
|
||||
// Map converting interface check.
|
||||
if v, ok := value.(iMapStrAny); ok {
|
||||
if v, ok := in.Value.(iMapStrAny); ok {
|
||||
// Value copy, in case of concurrent safety.
|
||||
for mapK, mapV := range v.MapStrAny() {
|
||||
if recursive {
|
||||
dataMap[mapK] = doMapConvertForMapOrStructValue(false, mapV, recursive, tags...)
|
||||
if in.RecursiveOption {
|
||||
dataMap[mapK] = doMapConvertForMapOrStructValue(
|
||||
doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: mapV,
|
||||
RecursiveType: in.RecursiveType,
|
||||
RecursiveOption: in.RecursiveType == recursiveTypeTrue,
|
||||
Tags: in.Tags,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
dataMap[mapK] = mapV
|
||||
}
|
||||
@ -247,7 +306,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
}
|
||||
mapKey = ""
|
||||
fieldTag := rtField.Tag
|
||||
for _, tag := range tags {
|
||||
for _, tag := range in.Tags {
|
||||
if mapKey = fieldTag.Get(tag); mapKey != "" {
|
||||
break
|
||||
}
|
||||
@ -274,7 +333,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
}
|
||||
}
|
||||
}
|
||||
if recursive || rtField.Anonymous {
|
||||
if in.RecursiveOption || rtField.Anonymous {
|
||||
// Do map converting recursively.
|
||||
var (
|
||||
rvAttrField = rvField
|
||||
@ -292,25 +351,48 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
continue
|
||||
}
|
||||
var (
|
||||
hasNoTag = mapKey == fieldName
|
||||
rvAttrInterface = rvAttrField.Interface()
|
||||
hasNoTag = mapKey == fieldName
|
||||
// DO NOT use rvAttrField.Interface() here,
|
||||
// as it might be changed from pointer to struct.
|
||||
rvInterface = rvField.Interface()
|
||||
)
|
||||
if hasNoTag && rtField.Anonymous {
|
||||
switch {
|
||||
case hasNoTag && rtField.Anonymous:
|
||||
// It means this attribute field has no tag.
|
||||
// Overwrite the attribute with sub-struct attribute fields.
|
||||
anonymousValue := doMapConvertForMapOrStructValue(false, rvAttrInterface, true, tags...)
|
||||
anonymousValue := doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: rvInterface,
|
||||
RecursiveType: in.RecursiveType,
|
||||
RecursiveOption: true,
|
||||
Tags: in.Tags,
|
||||
})
|
||||
if m, ok := anonymousValue.(map[string]interface{}); ok {
|
||||
for k, v := range m {
|
||||
dataMap[k] = v
|
||||
}
|
||||
} else {
|
||||
dataMap[mapKey] = rvAttrInterface
|
||||
dataMap[mapKey] = rvInterface
|
||||
}
|
||||
} else if !hasNoTag && rtField.Anonymous {
|
||||
// It means this attribute field has desired tag.
|
||||
dataMap[mapKey] = doMapConvertForMapOrStructValue(false, rvAttrInterface, true, tags...)
|
||||
} else {
|
||||
dataMap[mapKey] = doMapConvertForMapOrStructValue(false, rvAttrInterface, recursive, tags...)
|
||||
|
||||
// It means this attribute field has desired tag.
|
||||
case !hasNoTag && rtField.Anonymous:
|
||||
dataMap[mapKey] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: rvInterface,
|
||||
RecursiveType: in.RecursiveType,
|
||||
RecursiveOption: true,
|
||||
Tags: in.Tags,
|
||||
})
|
||||
|
||||
default:
|
||||
dataMap[mapKey] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: rvInterface,
|
||||
RecursiveType: in.RecursiveType,
|
||||
RecursiveOption: in.RecursiveType == recursiveTypeTrue,
|
||||
Tags: in.Tags,
|
||||
})
|
||||
}
|
||||
|
||||
// The struct attribute is type of slice.
|
||||
@ -323,7 +405,13 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
array := make([]interface{}, length)
|
||||
for arrayIndex := 0; arrayIndex < length; arrayIndex++ {
|
||||
array[arrayIndex] = doMapConvertForMapOrStructValue(
|
||||
false, rvAttrField.Index(arrayIndex), recursive, tags...,
|
||||
doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: rvAttrField.Index(arrayIndex),
|
||||
RecursiveType: in.RecursiveType,
|
||||
RecursiveOption: in.RecursiveType == recursiveTypeTrue,
|
||||
Tags: in.Tags,
|
||||
},
|
||||
)
|
||||
}
|
||||
dataMap[mapKey] = array
|
||||
@ -334,10 +422,13 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
)
|
||||
for _, k := range mapKeys {
|
||||
nestedMap[String(k.Interface())] = doMapConvertForMapOrStructValue(
|
||||
false,
|
||||
rvAttrField.MapIndex(k).Interface(),
|
||||
recursive,
|
||||
tags...,
|
||||
doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: rvAttrField.MapIndex(k).Interface(),
|
||||
RecursiveType: in.RecursiveType,
|
||||
RecursiveOption: in.RecursiveType == recursiveTypeTrue,
|
||||
Tags: in.Tags,
|
||||
},
|
||||
)
|
||||
}
|
||||
dataMap[mapKey] = nestedMap
|
||||
@ -358,7 +449,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
}
|
||||
}
|
||||
if len(dataMap) == 0 {
|
||||
return value
|
||||
return in.Value
|
||||
}
|
||||
return dataMap
|
||||
|
||||
@ -370,11 +461,17 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
}
|
||||
array := make([]interface{}, reflectValue.Len())
|
||||
for i := 0; i < length; i++ {
|
||||
array[i] = doMapConvertForMapOrStructValue(false, reflectValue.Index(i), recursive, tags...)
|
||||
array[i] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{
|
||||
IsRoot: false,
|
||||
Value: reflectValue.Index(i),
|
||||
RecursiveType: in.RecursiveType,
|
||||
RecursiveOption: in.RecursiveType == recursiveTypeTrue,
|
||||
Tags: in.Tags,
|
||||
})
|
||||
}
|
||||
return array
|
||||
}
|
||||
return value
|
||||
return in.Value
|
||||
}
|
||||
|
||||
// MapStrStr converts `value` to map[string]string.
|
||||
|
||||
@ -145,9 +145,29 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
|
||||
// If `params` and `pointer` are the same type, the do directly assignment.
|
||||
// For performance enhancement purpose.
|
||||
if pointerElemReflectValue.IsValid() && pointerElemReflectValue.Type() == paramsReflectValue.Type() {
|
||||
pointerElemReflectValue.Set(paramsReflectValue)
|
||||
return nil
|
||||
if pointerElemReflectValue.IsValid() {
|
||||
switch {
|
||||
// Eg:
|
||||
// UploadFile => UploadFile
|
||||
// *UploadFile => *UploadFile
|
||||
case pointerElemReflectValue.Type() == paramsReflectValue.Type():
|
||||
pointerElemReflectValue.Set(paramsReflectValue)
|
||||
return nil
|
||||
|
||||
// Eg:
|
||||
// UploadFile => *UploadFile
|
||||
case pointerElemReflectValue.Kind() == reflect.Ptr && pointerElemReflectValue.Elem().IsValid() &&
|
||||
pointerElemReflectValue.Elem().Type() == paramsReflectValue.Type():
|
||||
pointerElemReflectValue.Elem().Set(paramsReflectValue)
|
||||
return nil
|
||||
|
||||
// Eg:
|
||||
// *UploadFile => UploadFile
|
||||
case paramsReflectValue.Kind() == reflect.Ptr && paramsReflectValue.Elem().IsValid() &&
|
||||
pointerElemReflectValue.Type() == paramsReflectValue.Elem().Type():
|
||||
pointerElemReflectValue.Set(paramsReflectValue.Elem())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Normal unmarshalling interfaces checks.
|
||||
|
||||
@ -7,12 +7,12 @@
|
||||
package gconv_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
@ -41,103 +41,6 @@ func (s1 S1) Error() string {
|
||||
return "22222"
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/1227
|
||||
func Test_Issue1227(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type StructFromIssue1227 struct {
|
||||
Name string `json:"n1"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
origin interface{}
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Case1",
|
||||
origin: `{"n1":"n1"}`,
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case2",
|
||||
origin: `{"name":"name"}`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Case3",
|
||||
origin: `{"NaMe":"NaMe"}`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Case4",
|
||||
origin: g.Map{"n1": "n1"},
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case5",
|
||||
origin: g.Map{"NaMe": "n1"},
|
||||
want: "n1",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
p := StructFromIssue1227{}
|
||||
if err := gconv.Struct(tt.origin, &p); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Assert(p.Name, tt.want)
|
||||
}
|
||||
})
|
||||
|
||||
// Chinese key.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type StructFromIssue1227 struct {
|
||||
Name string `json:"中文Key"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
origin interface{}
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Case1",
|
||||
origin: `{"中文Key":"n1"}`,
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case2",
|
||||
origin: `{"Key":"name"}`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Case3",
|
||||
origin: `{"NaMe":"NaMe"}`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Case4",
|
||||
origin: g.Map{"中文Key": "n1"},
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case5",
|
||||
origin: g.Map{"中文KEY": "n1"},
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case5",
|
||||
origin: g.Map{"KEY": "n1"},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
p := StructFromIssue1227{}
|
||||
if err := gconv.Struct(tt.origin, &p); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Assert(p.Name, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Bool_All(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var any interface{} = nil
|
||||
@ -1545,76 +1448,3 @@ func Test_Struct_Time_All(t *testing.T) {
|
||||
t.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String())
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Issue1946(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type B struct {
|
||||
init *gtype.Bool
|
||||
Name string
|
||||
}
|
||||
type A struct {
|
||||
B *B
|
||||
}
|
||||
a := &A{
|
||||
B: &B{
|
||||
init: gtype.NewBool(true),
|
||||
},
|
||||
}
|
||||
err := gconv.Struct(g.Map{
|
||||
"B": g.Map{
|
||||
"Name": "init",
|
||||
},
|
||||
}, a)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a.B.Name, "init")
|
||||
t.Assert(a.B.init.Val(), true)
|
||||
})
|
||||
// It cannot change private attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type B struct {
|
||||
init *gtype.Bool
|
||||
Name string
|
||||
}
|
||||
type A struct {
|
||||
B *B
|
||||
}
|
||||
a := &A{
|
||||
B: &B{
|
||||
init: gtype.NewBool(true),
|
||||
},
|
||||
}
|
||||
err := gconv.Struct(g.Map{
|
||||
"B": g.Map{
|
||||
"init": 0,
|
||||
"Name": "init",
|
||||
},
|
||||
}, a)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a.B.Name, "init")
|
||||
t.Assert(a.B.init.Val(), true)
|
||||
})
|
||||
// It can change public attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type B struct {
|
||||
Init *gtype.Bool
|
||||
Name string
|
||||
}
|
||||
type A struct {
|
||||
B *B
|
||||
}
|
||||
a := &A{
|
||||
B: &B{
|
||||
Init: gtype.NewBool(),
|
||||
},
|
||||
}
|
||||
err := gconv.Struct(g.Map{
|
||||
"B": g.Map{
|
||||
"Init": 1,
|
||||
"Name": "init",
|
||||
},
|
||||
}, a)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a.B.Name, "init")
|
||||
t.Assert(a.B.Init.Val(), true)
|
||||
})
|
||||
}
|
||||
|
||||
225
util/gconv/gconv_z_unit_issue_test.go
Normal file
225
util/gconv/gconv_z_unit_issue_test.go
Normal file
@ -0,0 +1,225 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gconv_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// https://github.com/gogf/gf/issues/1227
|
||||
func Test_Issue1227(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type StructFromIssue1227 struct {
|
||||
Name string `json:"n1"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
origin interface{}
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Case1",
|
||||
origin: `{"n1":"n1"}`,
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case2",
|
||||
origin: `{"name":"name"}`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Case3",
|
||||
origin: `{"NaMe":"NaMe"}`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Case4",
|
||||
origin: g.Map{"n1": "n1"},
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case5",
|
||||
origin: g.Map{"NaMe": "n1"},
|
||||
want: "n1",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
p := StructFromIssue1227{}
|
||||
if err := gconv.Struct(tt.origin, &p); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Assert(p.Name, tt.want)
|
||||
}
|
||||
})
|
||||
|
||||
// Chinese key.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type StructFromIssue1227 struct {
|
||||
Name string `json:"中文Key"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
origin interface{}
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Case1",
|
||||
origin: `{"中文Key":"n1"}`,
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case2",
|
||||
origin: `{"Key":"name"}`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Case3",
|
||||
origin: `{"NaMe":"NaMe"}`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Case4",
|
||||
origin: g.Map{"中文Key": "n1"},
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case5",
|
||||
origin: g.Map{"中文KEY": "n1"},
|
||||
want: "n1",
|
||||
},
|
||||
{
|
||||
name: "Case5",
|
||||
origin: g.Map{"KEY": "n1"},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
p := StructFromIssue1227{}
|
||||
if err := gconv.Struct(tt.origin, &p); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Assert(p.Name, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Issue1946(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type B struct {
|
||||
init *gtype.Bool
|
||||
Name string
|
||||
}
|
||||
type A struct {
|
||||
B *B
|
||||
}
|
||||
a := &A{
|
||||
B: &B{
|
||||
init: gtype.NewBool(true),
|
||||
},
|
||||
}
|
||||
err := gconv.Struct(g.Map{
|
||||
"B": g.Map{
|
||||
"Name": "init",
|
||||
},
|
||||
}, a)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a.B.Name, "init")
|
||||
t.Assert(a.B.init.Val(), true)
|
||||
})
|
||||
// It cannot change private attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type B struct {
|
||||
init *gtype.Bool
|
||||
Name string
|
||||
}
|
||||
type A struct {
|
||||
B *B
|
||||
}
|
||||
a := &A{
|
||||
B: &B{
|
||||
init: gtype.NewBool(true),
|
||||
},
|
||||
}
|
||||
err := gconv.Struct(g.Map{
|
||||
"B": g.Map{
|
||||
"init": 0,
|
||||
"Name": "init",
|
||||
},
|
||||
}, a)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a.B.Name, "init")
|
||||
t.Assert(a.B.init.Val(), true)
|
||||
})
|
||||
// It can change public attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type B struct {
|
||||
Init *gtype.Bool
|
||||
Name string
|
||||
}
|
||||
type A struct {
|
||||
B *B
|
||||
}
|
||||
a := &A{
|
||||
B: &B{
|
||||
Init: gtype.NewBool(),
|
||||
},
|
||||
}
|
||||
err := gconv.Struct(g.Map{
|
||||
"B": g.Map{
|
||||
"Init": 1,
|
||||
"Name": "init",
|
||||
},
|
||||
}, a)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a.B.Name, "init")
|
||||
t.Assert(a.B.Init.Val(), true)
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/2381
|
||||
func Test_Issue2381(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Inherit struct {
|
||||
Id int64 `json:"id" description:"Id"`
|
||||
Flag *gjson.Json `json:"flag" description:"标签"`
|
||||
Title string `json:"title" description:"标题"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"`
|
||||
}
|
||||
type Test1 struct {
|
||||
Inherit
|
||||
}
|
||||
type Test2 struct {
|
||||
Inherit
|
||||
}
|
||||
var (
|
||||
a1 Test1
|
||||
a2 Test2
|
||||
)
|
||||
|
||||
a1 = Test1{
|
||||
Inherit{
|
||||
Id: 2,
|
||||
Flag: gjson.New("[1, 2]"),
|
||||
Title: "测试",
|
||||
CreatedAt: gtime.Now(),
|
||||
},
|
||||
}
|
||||
err := gconv.Scan(a1, &a2)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a1.Id, a2.Id)
|
||||
t.Assert(a1.Title, a2.Title)
|
||||
t.Assert(a1.CreatedAt, a2.CreatedAt)
|
||||
t.Assert(a1.Flag.String(), a2.Flag.String())
|
||||
})
|
||||
}
|
||||
@ -592,10 +592,10 @@ func TestMapsDeep(t *testing.T) {
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
string_interface_map_list := []map[string]interface{}{}
|
||||
string_interface_map_list = append(string_interface_map_list, map[string]interface{}{"id": 100})
|
||||
string_interface_map_list = append(string_interface_map_list, map[string]interface{}{"id": 200})
|
||||
list := gconv.MapsDeep(string_interface_map_list)
|
||||
stringInterfaceMapList := make([]map[string]interface{}, 0)
|
||||
stringInterfaceMapList = append(stringInterfaceMapList, map[string]interface{}{"id": 100})
|
||||
stringInterfaceMapList = append(stringInterfaceMapList, map[string]interface{}{"id": 200})
|
||||
list := gconv.MapsDeep(stringInterfaceMapList)
|
||||
t.Assert(len(list), 2)
|
||||
t.Assert(list[0]["id"], 100)
|
||||
t.Assert(list[1]["id"], 200)
|
||||
|
||||
@ -44,4 +44,5 @@ const (
|
||||
GConv = "gconv" // GConv defines the converting target name for specified struct field.
|
||||
GConvShort = "c" // GConv defines the converting target name for specified struct field.
|
||||
Json = "json" // Json tag is supported by stdlib.
|
||||
Security = "security" // Security defines scheme for authentication. Detail to see https://swagger.io/docs/specification/authentication/
|
||||
)
|
||||
|
||||
@ -81,11 +81,16 @@ func DumpTo(writer io.Writer, value interface{}, option DumpOption) {
|
||||
}
|
||||
|
||||
type doDumpOption struct {
|
||||
WithType bool
|
||||
ExportedOnly bool
|
||||
WithType bool
|
||||
ExportedOnly bool
|
||||
DumpedPointerSet map[string]struct{}
|
||||
}
|
||||
|
||||
func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDumpOption) {
|
||||
if option.DumpedPointerSet == nil {
|
||||
option.DumpedPointerSet = map[string]struct{}{}
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
buffer.WriteString(`<nil>`)
|
||||
return
|
||||
@ -111,26 +116,29 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
|
||||
var (
|
||||
reflectKind = reflectValue.Kind()
|
||||
reflectTypeName = reflectValue.Type().String()
|
||||
ptrAddress string
|
||||
newIndent = indent + dumpIndent
|
||||
)
|
||||
reflectTypeName = strings.ReplaceAll(reflectTypeName, `[]uint8`, `[]byte`)
|
||||
if !option.WithType {
|
||||
reflectTypeName = ""
|
||||
}
|
||||
for reflectKind == reflect.Ptr {
|
||||
if ptrAddress == "" {
|
||||
ptrAddress = fmt.Sprintf(`0x%x`, reflectValue.Pointer())
|
||||
}
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
var (
|
||||
exportInternalInput = doDumpInternalInput{
|
||||
Value: value,
|
||||
Indent: indent,
|
||||
NewIndent: newIndent,
|
||||
Buffer: buffer,
|
||||
Option: option,
|
||||
ReflectValue: reflectValue,
|
||||
ReflectTypeName: reflectTypeName,
|
||||
ExportedOnly: option.ExportedOnly,
|
||||
Value: value,
|
||||
Indent: indent,
|
||||
NewIndent: newIndent,
|
||||
Buffer: buffer,
|
||||
Option: option,
|
||||
PtrAddress: ptrAddress,
|
||||
ReflectValue: reflectValue,
|
||||
ReflectTypeName: reflectTypeName,
|
||||
ExportedOnly: option.ExportedOnly,
|
||||
DumpedPointerSet: option.DumpedPointerSet,
|
||||
}
|
||||
)
|
||||
switch reflectKind {
|
||||
@ -185,14 +193,16 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
|
||||
}
|
||||
|
||||
type doDumpInternalInput struct {
|
||||
Value interface{}
|
||||
Indent string
|
||||
NewIndent string
|
||||
Buffer *bytes.Buffer
|
||||
Option doDumpOption
|
||||
ReflectValue reflect.Value
|
||||
ReflectTypeName string
|
||||
ExportedOnly bool
|
||||
Value interface{}
|
||||
Indent string
|
||||
NewIndent string
|
||||
Buffer *bytes.Buffer
|
||||
Option doDumpOption
|
||||
ReflectValue reflect.Value
|
||||
ReflectTypeName string
|
||||
PtrAddress string
|
||||
ExportedOnly bool
|
||||
DumpedPointerSet map[string]struct{}
|
||||
}
|
||||
|
||||
func doDumpSlice(in doDumpInternalInput) {
|
||||
@ -295,6 +305,14 @@ func doDumpMap(in doDumpInternalInput) {
|
||||
}
|
||||
|
||||
func doDumpStruct(in doDumpInternalInput) {
|
||||
if in.PtrAddress != "" {
|
||||
if _, ok := in.DumpedPointerSet[in.PtrAddress]; ok {
|
||||
in.Buffer.WriteString(fmt.Sprintf(`<cycle dump %s>`, in.PtrAddress))
|
||||
return
|
||||
}
|
||||
}
|
||||
in.DumpedPointerSet[in.PtrAddress] = struct{}{}
|
||||
|
||||
structFields, _ := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: in.Value,
|
||||
RecursiveOption: gstructs.RecursiveOptionEmbedded,
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
@ -273,3 +274,17 @@ func Test_Dump_Issue1661(t *testing.T) {
|
||||
]`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Dump_Cycle_Attribute(t *testing.T) {
|
||||
type Abc struct {
|
||||
ab int
|
||||
cd *Abc
|
||||
}
|
||||
abc := Abc{ab: 3}
|
||||
abc.cd = &abc
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
g.DumpTo(buffer, abc, gutil.DumpOption{})
|
||||
t.Assert(gstr.Contains(buffer.String(), "cycle"), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,5 +2,5 @@ package gf
|
||||
|
||||
const (
|
||||
// VERSION is the current GoFrame version.
|
||||
VERSION = "v2.2.5"
|
||||
VERSION = "v2.2.6"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user