Compare commits

..

18 Commits

Author SHA1 Message Date
5a8b33fa09 fix gf.yaml (#2385)
* fix gf.yaml

* up
2023-01-03 14:33:41 +08:00
5884a0e05f fix issue #2381 (#2382)
* fix issue #2381

* up

* up
2023-01-03 11:00:23 +08:00
31e44062a8 revert from int64 to int for returning value of Count (#2378)
* revert from int64 to int for returning value of Count

* up

* up

* up
2022-12-30 16:54:43 +08:00
87cb1c9b8e add security tag support for openapi (#2377)
* support openapi path security

* add security path test case

* go format

* fix test case

* add doc for security
2022-12-29 20:56:20 +08:00
0266d24d0a fix Unknown setting charset for clickhouse driver (#2375) 2022-12-27 14:46:15 +08:00
0876e00eb8 fix issue in NewIntArrayRange function for package garray (#2374) 2022-12-26 19:28:01 +08:00
85c4794ceb fix BuildParams with urlEncode when len(v) <= 6 (#2308)
* fix: check urlEncode when len(v) <= 6

* fix BuildParams with urlEncode when len(v) <= 6

* fix BuildParams with urlEncode when len(v) <= 6

Co-authored-by: Prime Xiao <primexiao.dev@gmail.com>
2022-12-23 10:33:28 +08:00
e007bf35b2 parseConfigNodeLink support Chinese database name #2231 (#2238) 2022-12-22 17:33:51 +08:00
74e968e93b fix: ghttp server static path config (#2335) 2022-12-22 17:21:33 +08:00
18507fb836 由于 clickhouse 的 position的初始值为 1,导致gdb_core_utility.HasField 中对 fieldsArray 初始化出错 (#2346)
* 由于 clickhouse 的 position的初始值为 1,导致gdb_core_utility.HasField 中对 fieldsArray 初始化出错

* 修复单元测试

* 修复单元测试

* 补充单元测试

* 增加CK防御性代码

Co-authored-by: longl <longlei@dealmap.cloud>
Co-authored-by: houseme <housemecn@gmail.com>
2022-12-22 17:00:08 +08:00
3b245837b9 fix issue in cycle dumping for g.Dump (#2367)
* fix issue in cycle dumping for g.Dump

* up

* up

* up

Co-authored-by: houseme <housemecn@gmail.com>
2022-12-22 14:43:02 +08:00
a853984f52 fix issue #2334 when accessing static files with cache time (#2366)
* Solve the problem of error when accessing static files with cache time.
Error message:
2022-11-29 19:40:11.090 [ERRO] http: superfluous response.WriteHeader call from github.com/gogf/gf/v2/net/ghttp.(*ResponseWriter).Flush (ghttp_response_writer.go:58)
Stack:

Verification method:
curl 'http://127.0.0.1:8000/' -H 'If-Modified-Since: Thu, 08 Dec 2022 03:13:55 GMT' --compressed

* Solve the problem of error when accessing static files with cache time.
Error message:
2022-11-29 19:40:11.090 [ERRO] http: superfluous response.WriteHeader call from github.com/gogf/gf/v2/net/ghttp.(*ResponseWriter).Flush (ghttp_response_writer.go:58)
Stack:

Verification method:
curl 'http://127.0.0.1:8000/' -H 'If-Modified-Since: Thu, 08 Dec 2022 03:13:55 GMT' --compressed

* Solve the problem of error when accessing static files with cache time.
Error message:
2022-11-29 19:40:11.090 [ERRO] http: superfluous response.WriteHeader call from github.com/gogf/gf/v2/net/ghttp.(*ResponseWriter).Flush (ghttp_response_writer.go:58)
Stack:

Verification method:
curl 'http://127.0.0.1:8000/' -H 'If-Modified-Since: Thu, 08 Dec 2022 03:13:55 GMT' --compressed

* fix issue #2334 when accessing static files with cache time

* up

Co-authored-by: 曾洪亮 <hongliang.zeng@i-soft.com.cn>
Co-authored-by: houseme <housemecn@gmail.com>
2022-12-22 10:25:30 +08:00
00c544ee99 fix issue when only one file was uploaded in batch receiver attribute (#2365)
* fix fixed An error occurred when only one file was uploaded in batches and add unit testing(#2092)

* fix issue uploading files for ghttp.Server

Co-authored-by: yxh <yxh1103@qq.com>
2022-12-21 10:38:19 +08:00
e7b9e41a5e improve ut case for package internal/rwmutex (#2364) 2022-12-20 15:56:29 +08:00
e254b4f3c0 add ut cases for package gcache (#2341)
* gTcp Example Function:
1.NewConn 2.NewConnTLS 3.NewConnKeyCrt

* gTcp Example Function:
1.Send

* add example function ExampleConn_Recv and ExampleConn_RecvWithTimeout

* add example function
1. ExampleConn_SendWithTimeout
2. ExampleConn_RecvLine
3. ExampleConn_RecvTill

* add example function
1. ExampleConn_SendRecv
2. ExampleConn_SendRecvWithTimeout
3. ExampleConn_SetDeadline
4. ExampleConn_SetReceiveBufferWait

* add gtcp test function
1. Test_Package_Option_HeadSize4
2. Test_Package_Option_Error

* add gtcp example function
1. ExampleGetFreePorts
2. ExampleSend
3. ExampleSendRecv
4. ExampleSendWithTimeout
5. ExampleSendRecvWithTimeout
6. ExampleMustGetFreePort

* add gtcp example function
1. ExampleSendPkg
2. ExampleSendRecvPkg
3. ExampleSendPkgWithTimeout
4. ExampleSendRecvPkgWithTimeout

* add gtcp test function
1. Test_Pool_Send
2. Test_Pool_Recv
3. Test_Pool_RecvLine
4. Test_Pool_RecvTill
5. Test_Pool_RecvWithTimeout
6. Test_Pool_SendWithTimeout
7. Test_Pool_SendRecvWithTimeout

* fix

* add gtcp example function
1. ExampleGetServer
2. ExampleSetAddress
3. ExampleSetHandler
4. ExampleRun_NilHandle

* exec CI

* exec CI

* exec CI

* modify test server address

* modify and exec CI

* modify and exec CI

* modify and exec CI

* modify and exec CI

* modify and exec CI

* modify and exec CI

* add example funcion ExampleConn_Recv_Once and fix

* fix

* add some error case in example function

* add some error case in example function

* 1.add example function ExampleNewServerKeyCrt
2.add function SendRecvPkgWithTimeout unit test

* add function Test_Server_NewServerKeyCrt unit test

* revert

* add function Test_Package_Timeout, Test_Package_Option_HeadSize3, Test_Conn_RecvPkgError unit test

* fix

* add example function
1.ExampleClient_Clone
2.ExampleLoadKeyCrt

* add example function
1.ExampleNewNetConnKeyCrt

* fix

* add example function
1.ExampleClient_DeleteBytes
2.ExampleClient_HeadBytes
3.ExampleClient_PatchBytes
4.ExampleClient_ConnectBytes
5.ExampleClient_OptionsBytes
6.ExampleClient_TraceBytes
7.ExampleClient_PutBytes

* add example function
1.ExampleClient_Prefix
2.ExampleClient_Retry
3.ExampleClient_RedirectLimit

* add example function
1.ExampleClient_SetBrowserMode
2.ExampleClient_SetHeader
3.ExampleClient_SetRedirectLimit

* add example function
1.ExampleClient_SetTLSKeyCrt
2.ExampleClient_SetTLSConfig
modify example funcion
1.ExampleClient_SetProxy
2.ExampleClient_Proxy

* add example function
1.ExampleClient_PutContent
2.ExampleClient_DeleteContent
3.ExampleClient_HeadContent
4.ExampleClient_PatchContent
5.ExampleClient_ConnectContent
6.ExampleClient_OptionsContent
7.ExampleClient_TraceContent
8.ExampleClient_RequestContent

* add example function
1.ExampleClient_RawRequest

* add unit function
1.TestGetFreePorts
2.TestNewConn
3.TestNewConnTLS
4.TestNewConnKeyCrt
5.TestConn_SendWithTimeout

* add unit function
1.TestConn_Send
2.TestConn_SendRecv
3.TestConn_SendRecvWithTimeout

* modify

* modify

* add example function
1.TestConn_SetReceiveBufferWait
2.TestNewNetConnKeyCrt
3.TestSend

* add example function
1.TestSendRecv
2.TestSendWithTimeout

* add unit function
1.TestMustGetFreePort
2.TestSendRecvWithTimeout
3.TestSendPkg

* add client recevied server's response content assert

* modify

* modify

* add example function
1.TestSendRecvPkg
2.TestSendPkgWithTimeout
3.TestSendRecvPkgWithTimeout

* add GetAddress() function
add unit funciton
1.TestNewServer
2.TestGetServer
3.TestServer_SetAddress
4.TestServer_SetHandler
5.TestServer_Run

* modify

* modify

* add unit funciton
1.TestLoadKeyCrt

* modify

* delete function fromHex

* add gclient dump unit test

* add example function
1.ExampleClient_Put
2.ExampleClient_Delete
3.ExampleClient_Head
4.ExampleClient_Patch
5.ExampleClient_Connect
6.ExampleClient_Options
7.ExampleClient_Trace

* add example function
1.TestClient_DoRequest

* add example function
1.ExampleClient_PutVar
2.ExampleClient_DeleteVar
3.ExampleClient_HeadVar
4.ExampleClient_PatchVar
5.ExampleClient_ConnectVar
6.ExampleClient_OptionsVar
7.ExampleClient_TraceVar

* modify

* modify

* add CustomProvider function

* modify

* add unit funciton
1.Test_NewConn
2.Test_GetFreePorts

* add unit funciton
1.Test_Server

* garray_normal_any code converage

* garray_normal_int code converage

* garray_normal_str code converage

* garray_sorted_any code converage

* garray_sorted_int code converage

* garray_sorted_str code converage

* glist code converage

* gmap, gmap_hash_any_any_map code converage

* gmap_hash_int_any_map code converage

* gmap_hash_int_any_map code converage

* gmap_hash_int_int_map code converage

* gmap_hash_int_str_map code converage

* gmap_hash_str_any_map code converage

* gmap_hash_str_int_map code converage

* gmap_hash_str_str_map code converage

* gmap_list_map code converage

* gmap_list_map code converage

* revert gf.yml

* add gtest unit test function

* add ut cases for package gcache

* add ut cases for package gcache

* add ut cases for package gcache

* add ut cases for package gcache

* add ut cases for package gcache

* modify

Co-authored-by: John Guo <john@johng.cn>
2022-12-20 14:49:31 +08:00
b0c9c68c9c add ut cases for package ghttp_request (#2351)
* add ut cases for package ghttp_middleware

* add ut cases for package ghttp_request

* add ut cases for package ghttp_request

* add ut cases for package ghttp_request

* add ut cases for package ghttp_request - form

* add ut cases for package ghttp_request - query

* add ut cases for package ghttp_request - request

* add ut cases for package ghttp_request - router
2022-12-12 10:28:58 +08:00
1030434ce6 add ut cases for package ghttp_response (#2352)
* add ut cases for package ghttp_response

* add ut cases for package ghttp_response

* add ut cases for package ghttp_response
2022-12-12 10:28:35 +08:00
2f08c4b00f add ut cases for package ghttp_middleware and ghttp_request (#2344)
* add ut cases for package ghttp_middleware

* add ut cases for package ghttp_request

* add ut cases for package ghttp_request
2022-12-07 20:02:46 +08:00
51 changed files with 2180 additions and 425 deletions

View File

@ -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

View File

@ -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...)

View File

@ -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...)

View File

@ -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...)

View File

@ -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...)

View File

@ -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() {

View File

@ -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]`)
})
}

View File

@ -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()
}

View File

@ -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(),

View File

@ -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)
})
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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")
})
}

View File

@ -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&...&paramN=valueN]
linkPattern = `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([\w\-]*)\?{0,1}(.*)`
linkPattern = `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)`
)
const (

View File

@ -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.

View File

@ -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
}

View File

@ -208,5 +208,8 @@ func (j *Json) Dump() {
}
j.mu.RLock()
defer j.mu.RUnlock()
if j.p == nil {
return
}
gutil.Dump(*j.p)
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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()))

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View 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)
})
}

View File

@ -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()
})
}

View File

@ -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")
})
}

View File

@ -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))
})
}

View File

@ -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)
})
}

View File

@ -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)
})
}

View 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>")
})
}

View File

@ -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")

View File

@ -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)
})
}

View File

@ -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)
}
})
}

View File

@ -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.
// =================================================================================================================

View File

@ -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)
})
}

View File

@ -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)
})
}

View File

@ -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)
})
}

View File

@ -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) {

View File

@ -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)

View File

@ -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) {

View File

@ -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.

View File

@ -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.

View File

@ -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)
})
}

View 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())
})
}

View File

@ -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)

View File

@ -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/
)

View File

@ -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,

View File

@ -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)
})
}

View File

@ -2,5 +2,5 @@ package gf
const (
// VERSION is the current GoFrame version.
VERSION = "v2.2.5"
VERSION = "v2.2.6"
)