mirror of
https://gitee.com/johng/gf.git
synced 2024-11-30 03:07:45 +08:00
Merge branch 'gogf:master' into master
This commit is contained in:
commit
413a8be037
@ -59,7 +59,7 @@ func New(limit ...int) *Queue {
|
||||
}
|
||||
|
||||
// asyncLoopFromListToChannel starts an asynchronous goroutine,
|
||||
// which handles the data synchronization from list <q.list> to channel <q.C>.
|
||||
// which handles the data synchronization from list `q.list` to channel `q.C`.
|
||||
func (q *Queue) asyncLoopFromListToChannel() {
|
||||
defer func() {
|
||||
if q.closed.Val() {
|
||||
@ -87,13 +87,13 @@ func (q *Queue) asyncLoopFromListToChannel() {
|
||||
<-q.events
|
||||
}
|
||||
}
|
||||
// It should be here to close q.C if `q` is unlimited size.
|
||||
// It should be here to close `q.C` if `q` is unlimited size.
|
||||
// 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 panics if Push is called after the queue is closed.
|
||||
// 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
|
||||
@ -121,14 +121,15 @@ func (q *Queue) Close() {
|
||||
}
|
||||
if q.limit > 0 {
|
||||
close(q.C)
|
||||
}
|
||||
for i := 0; i < defaultBatchSize; i++ {
|
||||
q.Pop()
|
||||
} 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 a
|
||||
// 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 {
|
||||
|
135
container/gqueue/gqueue_z_example_test.go
Normal file
135
container/gqueue/gqueue_z_example_test.go
Normal file
@ -0,0 +1,135 @@
|
||||
// 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 gqueue_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/container/gqueue"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleNew() {
|
||||
n := 10
|
||||
q := gqueue.New()
|
||||
|
||||
// Producer
|
||||
for i := 0; i < n; i++ {
|
||||
q.Push(i)
|
||||
}
|
||||
|
||||
fmt.Println(q.Len())
|
||||
|
||||
// Close the queue in three seconds.
|
||||
gtimer.SetTimeout(context.Background(), time.Second*3, func(ctx context.Context) {
|
||||
q.Close()
|
||||
})
|
||||
|
||||
// The consumer constantly reads the queue data.
|
||||
// If there is no data in the queue, it will block.
|
||||
// The queue is read using the queue.C property exposed
|
||||
// by the queue object and the selectIO multiplexing syntax
|
||||
// example:
|
||||
// for {
|
||||
// select {
|
||||
// case v := <-queue.C:
|
||||
// if v != nil {
|
||||
// fmt.Println(v)
|
||||
// } else {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
for {
|
||||
if v := q.Pop(); v != nil {
|
||||
fmt.Print(v)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 10
|
||||
// 0123456789
|
||||
}
|
||||
|
||||
func ExampleQueue_Push() {
|
||||
q := gqueue.New()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
q.Push(i)
|
||||
}
|
||||
|
||||
fmt.Println(q.Pop())
|
||||
fmt.Println(q.Pop())
|
||||
fmt.Println(q.Pop())
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 1
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleQueue_Pop() {
|
||||
q := gqueue.New()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
q.Push(i)
|
||||
}
|
||||
|
||||
fmt.Println(q.Pop())
|
||||
q.Close()
|
||||
fmt.Println(q.Pop())
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// <nil>
|
||||
}
|
||||
|
||||
func ExampleQueue_Close() {
|
||||
q := gqueue.New()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
q.Push(i)
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
q.Close()
|
||||
|
||||
fmt.Println(q.Len())
|
||||
fmt.Println(q.Pop())
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// <nil>
|
||||
}
|
||||
|
||||
func ExampleQueue_Len() {
|
||||
q := gqueue.New()
|
||||
|
||||
q.Push(1)
|
||||
q.Push(2)
|
||||
|
||||
fmt.Println(q.Len())
|
||||
|
||||
// May Output:
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleQueue_Size() {
|
||||
q := gqueue.New()
|
||||
|
||||
q.Push(1)
|
||||
q.Push(2)
|
||||
|
||||
// Size is alias of Len.
|
||||
fmt.Println(q.Size())
|
||||
|
||||
// May Output:
|
||||
// 2
|
||||
}
|
@ -9,10 +9,8 @@ package gdb
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
@ -45,8 +43,7 @@ type Model struct {
|
||||
distinct string // Force the query to only return distinct results.
|
||||
lockInfo string // Lock for update or in shared lock.
|
||||
cacheEnabled bool // Enable sql result cache feature.
|
||||
cacheDuration time.Duration // Cache TTL duration (< 1 for removing cache, >= 0 for saving cache).
|
||||
cacheName string // Cache name for custom operation.
|
||||
cacheOption CacheOption // Cache option for query statement.
|
||||
unscoped bool // Disables soft deleting features when select/delete operations.
|
||||
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
|
||||
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
|
||||
|
@ -11,26 +11,32 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type CacheOption struct {
|
||||
// Duration is the TTL for the cache.
|
||||
// If the parameter `Duration` < 0, which means it clear the cache with given `Name`.
|
||||
// If the parameter `Duration` = 0, which means it never expires.
|
||||
// If the parameter `Duration` > 0, which means it expires after `Duration`.
|
||||
Duration time.Duration
|
||||
|
||||
// Name is an optional unique name for the cache.
|
||||
// The Name is used to bind a name to the cache, which means you can later control the cache
|
||||
// like changing the `duration` or clearing the cache with specified Name.
|
||||
Name string
|
||||
|
||||
// Force caches the query result whatever the result is nil or not.
|
||||
// It is used to avoid Cache Penetration.
|
||||
Force bool
|
||||
}
|
||||
|
||||
// Cache sets the cache feature for the model. It caches the result of the sql, which means
|
||||
// if there's another same sql request, it just reads and returns the result from cache, it
|
||||
// but not committed and executed into the database.
|
||||
//
|
||||
// If the parameter `duration` < 0, which means it clear the cache with given `name`.
|
||||
// If the parameter `duration` = 0, which means it never expires.
|
||||
// If the parameter `duration` > 0, which means it expires after `duration`.
|
||||
//
|
||||
// The optional parameter `name` is used to bind a name to the cache, which means you can
|
||||
// later control the cache like changing the `duration` or clearing the cache with specified
|
||||
// `name`.
|
||||
//
|
||||
// Note that, the cache feature is disabled if the model is performing select statement
|
||||
// on a transaction.
|
||||
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
|
||||
func (m *Model) Cache(option CacheOption) *Model {
|
||||
model := m.getModel()
|
||||
model.cacheDuration = duration
|
||||
if len(name) > 0 {
|
||||
model.cacheName = name[0]
|
||||
}
|
||||
model.cacheOption = option
|
||||
model.cacheEnabled = true
|
||||
return model
|
||||
}
|
||||
@ -38,9 +44,9 @@ func (m *Model) Cache(duration time.Duration, name ...string) *Model {
|
||||
// checkAndRemoveCache checks and removes the cache in insert/update/delete statement if
|
||||
// cache feature is enabled.
|
||||
func (m *Model) checkAndRemoveCache() {
|
||||
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
|
||||
if m.cacheEnabled && m.cacheOption.Duration < 0 && len(m.cacheOption.Name) > 0 {
|
||||
var ctx = m.GetCtx()
|
||||
_, err := m.db.GetCache().Remove(ctx, m.cacheName)
|
||||
_, err := m.db.GetCache().Remove(ctx, m.cacheOption.Name)
|
||||
if err != nil {
|
||||
intlog.Error(ctx, err)
|
||||
}
|
||||
|
@ -437,7 +437,7 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
|
||||
)
|
||||
// Retrieve from cache.
|
||||
if m.cacheEnabled && m.tx == nil {
|
||||
cacheKey = m.cacheName
|
||||
cacheKey = m.cacheOption.Name
|
||||
if len(cacheKey) == 0 {
|
||||
cacheKey = sql + ", @PARAMS:" + gconv.String(args)
|
||||
}
|
||||
@ -461,16 +461,16 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
|
||||
)
|
||||
// Cache the result.
|
||||
if cacheKey != "" && err == nil {
|
||||
if m.cacheDuration < 0 {
|
||||
if m.cacheOption.Duration < 0 {
|
||||
if _, err := cacheObj.Remove(ctx, cacheKey); err != nil {
|
||||
intlog.Error(m.GetCtx(), err)
|
||||
}
|
||||
} else {
|
||||
// In case of Cache Penetration.
|
||||
if result == nil {
|
||||
if result.IsEmpty() && m.cacheOption.Force {
|
||||
result = Result{}
|
||||
}
|
||||
if err := cacheObj.Set(ctx, cacheKey, result, m.cacheDuration); err != nil {
|
||||
if err := cacheObj.Set(ctx, cacheKey, result, m.cacheOption.Duration); err != nil {
|
||||
intlog.Error(m.GetCtx(), err)
|
||||
}
|
||||
}
|
||||
|
@ -1776,7 +1776,7 @@ CREATE TABLE %s (
|
||||
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["uid"], 3)
|
||||
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["score"], 5)
|
||||
})
|
||||
db.SetDebug(true)
|
||||
|
||||
// Result ScanList with struct elements and pointer attributes.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []Entity
|
||||
|
@ -2479,7 +2479,11 @@ func Test_Model_Cache(t *testing.T) {
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Model(table).Cache(time.Second, "test1").WherePri(1).One()
|
||||
one, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test1",
|
||||
Force: false,
|
||||
}).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_1")
|
||||
|
||||
@ -2489,63 +2493,107 @@ func Test_Model_Cache(t *testing.T) {
|
||||
t.AssertNil(err)
|
||||
t.Assert(n, 1)
|
||||
|
||||
one, err = db.Model(table).Cache(time.Second, "test1").WherePri(1).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test1",
|
||||
Force: false,
|
||||
}).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_1")
|
||||
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
one, err = db.Model(table).Cache(time.Second, "test1").WherePri(1).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test1",
|
||||
Force: false,
|
||||
}).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_100")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Model(table).Cache(time.Second, "test2").WherePri(2).One()
|
||||
one, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test2",
|
||||
Force: false,
|
||||
}).WherePri(2).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_2")
|
||||
|
||||
r, err := db.Model(table).Data("passport", "user_200").Cache(-1, "test2").WherePri(2).Update()
|
||||
r, err := db.Model(table).Data("passport", "user_200").Cache(gdb.CacheOption{
|
||||
Duration: -1,
|
||||
Name: "test2",
|
||||
Force: false,
|
||||
}).WherePri(2).Update()
|
||||
t.AssertNil(err)
|
||||
n, err := r.RowsAffected()
|
||||
t.AssertNil(err)
|
||||
t.Assert(n, 1)
|
||||
|
||||
one, err = db.Model(table).Cache(time.Second, "test2").WherePri(2).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test2",
|
||||
Force: false,
|
||||
}).WherePri(2).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_200")
|
||||
})
|
||||
// transaction.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// make cache for id 3
|
||||
one, err := db.Model(table).Cache(time.Second, "test3").WherePri(3).One()
|
||||
one, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(3).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_3")
|
||||
|
||||
r, err := db.Model(table).Data("passport", "user_300").Cache(time.Second, "test3").WherePri(3).Update()
|
||||
r, err := db.Model(table).Data("passport", "user_300").Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(3).Update()
|
||||
t.AssertNil(err)
|
||||
n, err := r.RowsAffected()
|
||||
t.AssertNil(err)
|
||||
t.Assert(n, 1)
|
||||
|
||||
err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
|
||||
one, err := tx.Model(table).Cache(time.Second, "test3").WherePri(3).One()
|
||||
one, err := tx.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(3).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_300")
|
||||
return nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err = db.Model(table).Cache(time.Second, "test3").WherePri(3).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(3).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_3")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// make cache for id 4
|
||||
one, err := db.Model(table).Cache(time.Second, "test4").WherePri(4).One()
|
||||
one, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test4",
|
||||
Force: false,
|
||||
}).WherePri(4).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_4")
|
||||
|
||||
r, err := db.Model(table).Data("passport", "user_400").Cache(time.Second, "test3").WherePri(4).Update()
|
||||
r, err := db.Model(table).Data("passport", "user_400").Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(4).Update()
|
||||
t.AssertNil(err)
|
||||
n, err := r.RowsAffected()
|
||||
t.AssertNil(err)
|
||||
@ -2553,12 +2601,20 @@ func Test_Model_Cache(t *testing.T) {
|
||||
|
||||
err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
|
||||
// Cache feature disabled.
|
||||
one, err := tx.Model(table).Cache(time.Second, "test4").WherePri(4).One()
|
||||
one, err := tx.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test4",
|
||||
Force: false,
|
||||
}).WherePri(4).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_400")
|
||||
// Update the cache.
|
||||
r, err := tx.Model(table).Data("passport", "user_4000").
|
||||
Cache(-1, "test4").WherePri(4).Update()
|
||||
Cache(gdb.CacheOption{
|
||||
Duration: -1,
|
||||
Name: "test4",
|
||||
Force: false,
|
||||
}).WherePri(4).Update()
|
||||
t.AssertNil(err)
|
||||
n, err := r.RowsAffected()
|
||||
t.AssertNil(err)
|
||||
@ -2567,7 +2623,11 @@ func Test_Model_Cache(t *testing.T) {
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Read from db.
|
||||
one, err = db.Model(table).Cache(time.Second, "test4").WherePri(4).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test4",
|
||||
Force: false,
|
||||
}).WherePri(4).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_4000")
|
||||
})
|
||||
|
@ -60,7 +60,7 @@ func Server(name ...interface{}) *ghttp.Server {
|
||||
}
|
||||
} else {
|
||||
// The configuration is not necessary, so it just prints internal logs.
|
||||
intlog.Printf(ctx, `missing configuration for HTTP server "%s"`, instanceName)
|
||||
intlog.Printf(ctx, `missing configuration from configuration component for HTTP server "%s"`, instanceName)
|
||||
}
|
||||
|
||||
// Server logger configuration checks.
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
|
||||
// IsNil checks whether `value` is nil.
|
||||
func IsNil(value interface{}) bool {
|
||||
return value == nil
|
||||
return empty.IsNil(value)
|
||||
}
|
||||
|
||||
// IsEmpty checks whether `value` is empty.
|
||||
|
37
internal/utils/utils_list.go
Normal file
37
internal/utils/utils_list.go
Normal file
@ -0,0 +1,37 @@
|
||||
// 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 utils
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ListToMapByKey converts `list` to a map[string]interface{} of which key is specified by `key`.
|
||||
// Note that the item value may be type of slice.
|
||||
func ListToMapByKey(list []map[string]interface{}, key string) map[string]interface{} {
|
||||
var (
|
||||
s = ""
|
||||
m = make(map[string]interface{})
|
||||
tempMap = make(map[string][]interface{})
|
||||
hasMultiValues bool
|
||||
)
|
||||
for _, item := range list {
|
||||
if k, ok := item[key]; ok {
|
||||
s = fmt.Sprintf(`%v`, k)
|
||||
tempMap[s] = append(tempMap[s], item)
|
||||
if len(tempMap[s]) > 1 {
|
||||
hasMultiValues = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range tempMap {
|
||||
if hasMultiValues {
|
||||
m[k] = v
|
||||
} else {
|
||||
m[k] = v[0]
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
26
internal/utils/utils_map.go
Normal file
26
internal/utils/utils_map.go
Normal file
@ -0,0 +1,26 @@
|
||||
// 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 utils
|
||||
|
||||
// MapPossibleItemByKey tries to find the possible key-value pair for given key ignoring cases and symbols.
|
||||
//
|
||||
// Note that this function might be of low performance.
|
||||
func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey string, foundValue interface{}) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
if v, ok := data[key]; ok {
|
||||
return key, v
|
||||
}
|
||||
// Loop checking.
|
||||
for k, v := range data {
|
||||
if EqualFoldWithoutChars(k, key) {
|
||||
return k, v
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
@ -35,9 +35,15 @@ func (d *Domain) BindHandler(pattern string, handler interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Domain) doBindHandler(ctx context.Context, pattern string, funcInfo handlerFuncInfo, middleware []HandlerFunc, source string) {
|
||||
func (d *Domain) doBindHandler(ctx context.Context, in doBindHandlerInput) {
|
||||
for domain, _ := range d.domains {
|
||||
d.server.doBindHandler(ctx, pattern+"@"+domain, funcInfo, middleware, source)
|
||||
d.server.doBindHandler(ctx, doBindHandlerInput{
|
||||
Prefix: in.Prefix,
|
||||
Pattern: in.Pattern + "@" + domain,
|
||||
FuncInfo: in.FuncInfo,
|
||||
Middleware: in.Middleware,
|
||||
Source: in.Source,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,9 +53,16 @@ func (d *Domain) BindObject(pattern string, obj interface{}, methods ...string)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Domain) doBindObject(ctx context.Context, pattern string, obj interface{}, methods string, middleware []HandlerFunc, source string) {
|
||||
func (d *Domain) doBindObject(ctx context.Context, in doBindObjectInput) {
|
||||
for domain, _ := range d.domains {
|
||||
d.server.doBindObject(ctx, pattern+"@"+domain, obj, methods, middleware, source)
|
||||
d.server.doBindObject(ctx, doBindObjectInput{
|
||||
Prefix: in.Prefix,
|
||||
Pattern: in.Pattern + "@" + domain,
|
||||
Object: in.Object,
|
||||
Method: in.Method,
|
||||
Middleware: in.Middleware,
|
||||
Source: in.Source,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,13 +72,16 @@ func (d *Domain) BindObjectMethod(pattern string, obj interface{}, method string
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Domain) doBindObjectMethod(
|
||||
ctx context.Context,
|
||||
pattern string, obj interface{}, method string,
|
||||
middleware []HandlerFunc, source string,
|
||||
) {
|
||||
func (d *Domain) doBindObjectMethod(ctx context.Context, in doBindObjectMethodInput) {
|
||||
for domain, _ := range d.domains {
|
||||
d.server.doBindObjectMethod(ctx, pattern+"@"+domain, obj, method, middleware, source)
|
||||
d.server.doBindObjectMethod(ctx, doBindObjectMethodInput{
|
||||
Prefix: in.Prefix,
|
||||
Pattern: in.Pattern + "@" + domain,
|
||||
Object: in.Object,
|
||||
Method: in.Method,
|
||||
Middleware: in.Middleware,
|
||||
Source: in.Source,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,9 +91,16 @@ func (d *Domain) BindObjectRest(pattern string, obj interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Domain) doBindObjectRest(ctx context.Context, pattern string, obj interface{}, middleware []HandlerFunc, source string) {
|
||||
func (d *Domain) doBindObjectRest(ctx context.Context, in doBindObjectInput) {
|
||||
for domain, _ := range d.domains {
|
||||
d.server.doBindObjectRest(ctx, pattern+"@"+domain, obj, middleware, source)
|
||||
d.server.doBindObjectRest(ctx, doBindObjectInput{
|
||||
Prefix: in.Prefix,
|
||||
Pattern: in.Pattern + "@" + domain,
|
||||
Object: in.Object,
|
||||
Method: in.Method,
|
||||
Middleware: in.Middleware,
|
||||
Source: in.Source,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,9 +110,15 @@ func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFun
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Domain) doBindHookHandler(ctx context.Context, pattern string, hook string, handler HandlerFunc, source string) {
|
||||
func (d *Domain) doBindHookHandler(ctx context.Context, in doBindHookHandlerInput) {
|
||||
for domain, _ := range d.domains {
|
||||
d.server.doBindHookHandler(ctx, pattern+"@"+domain, hook, handler, source)
|
||||
d.server.doBindHookHandler(ctx, doBindHookHandlerInput{
|
||||
Prefix: in.Prefix,
|
||||
Pattern: in.Pattern + "@" + domain,
|
||||
HookName: in.HookName,
|
||||
Handler: in.Handler,
|
||||
Source: in.Source,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,9 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/protocol/goai"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/debug/gdebug"
|
||||
@ -63,11 +66,22 @@ func (s *Server) parsePattern(pattern string) (domain, method, path string, err
|
||||
return
|
||||
}
|
||||
|
||||
type setHandlerInput struct {
|
||||
Prefix string
|
||||
Pattern string
|
||||
HandlerItem *handlerItem
|
||||
}
|
||||
|
||||
// setHandler creates router item with given handler and pattern and registers the handler to the router tree.
|
||||
// The router tree can be treated as a multilayer hash table, please refer to the comment in following codes.
|
||||
// This function is called during server starts up, which cares little about the performance. What really cares
|
||||
// is the well-designed router storage structure for router searching when the request is under serving.
|
||||
func (s *Server) setHandler(ctx context.Context, pattern string, handler *handlerItem) {
|
||||
func (s *Server) setHandler(ctx context.Context, in setHandlerInput) {
|
||||
var (
|
||||
prefix = in.Prefix
|
||||
pattern = in.Pattern
|
||||
handler = in.HandlerItem
|
||||
)
|
||||
handler.Id = handlerIdGenerator.Add(1)
|
||||
if handler.Source == "" {
|
||||
_, file, line := gdebug.CallerWithFilter(stackFilterKey)
|
||||
@ -78,6 +92,28 @@ func (s *Server) setHandler(ctx context.Context, pattern string, handler *handle
|
||||
s.Logger().Fatalf(ctx, `invalid pattern "%s", %+v`, pattern, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Change the registered route according meta info from its request structure.
|
||||
if handler.Info.Type != nil && handler.Info.Type.NumIn() == 2 {
|
||||
var (
|
||||
objectReq = reflect.New(handler.Info.Type.In(1))
|
||||
)
|
||||
if v := gmeta.Get(objectReq, goai.TagNamePath); !v.IsEmpty() {
|
||||
uri = v.String()
|
||||
}
|
||||
if v := gmeta.Get(objectReq, goai.TagNameMethod); !v.IsEmpty() {
|
||||
method = v.String()
|
||||
}
|
||||
if v := gmeta.Get(objectReq, goai.TagNameDomain); !v.IsEmpty() {
|
||||
domain = v.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Prefix for URI feature.
|
||||
if prefix != "" {
|
||||
uri = prefix + "/" + strings.TrimLeft(uri, "/")
|
||||
}
|
||||
|
||||
if len(uri) == 0 || uri[0] != '/' {
|
||||
s.Logger().Fatalf(ctx, `invalid pattern "%s", URI should lead with '/'`, pattern)
|
||||
return
|
||||
@ -177,7 +213,7 @@ func (s *Server) setHandler(ctx context.Context, pattern string, handler *handle
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
item = e.Value.(*handlerItem)
|
||||
// Checks the priority whether inserting the route item before current item,
|
||||
// which means it has more higher priority.
|
||||
// which means it has higher priority.
|
||||
if s.compareRouterPriority(handler, item) {
|
||||
l.InsertBefore(e, handler)
|
||||
pushed = true
|
||||
@ -211,11 +247,11 @@ func (s *Server) setHandler(ctx context.Context, pattern string, handler *handle
|
||||
|
||||
// compareRouterPriority compares the priority between `newItem` and `oldItem`. It returns true
|
||||
// if `newItem`'s priority is higher than `oldItem`, else it returns false. The higher priority
|
||||
// item will be insert into the router list before the other one.
|
||||
// item will be inserted into the router list before the other one.
|
||||
//
|
||||
// Comparison rules:
|
||||
// 1. The middleware has the most high priority.
|
||||
// 2. URI: The deeper the higher (simply check the count of char '/' in the URI).
|
||||
// 2. URI: The deeper, the higher (simply check the count of char '/' in the URI).
|
||||
// 3. Route type: {xxx} > :xxx > *xxx.
|
||||
func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerItem) bool {
|
||||
// If they're all type of middleware, the priority is according their registered sequence.
|
||||
@ -226,7 +262,7 @@ func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerIte
|
||||
if newItem.Type == HandlerTypeMiddleware && oldItem.Type != HandlerTypeMiddleware {
|
||||
return true
|
||||
}
|
||||
// URI: The deeper the higher (simply check the count of char '/' in the URI).
|
||||
// URI: The deeper, the higher (simply check the count of char '/' in the URI).
|
||||
if newItem.Router.Priority > oldItem.Router.Priority {
|
||||
return true
|
||||
}
|
||||
|
@ -10,10 +10,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/debug/gdebug"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
@ -28,9 +27,6 @@ type (
|
||||
middleware []HandlerFunc // Middleware array.
|
||||
}
|
||||
|
||||
// GroupItem is item for router group.
|
||||
GroupItem = []interface{}
|
||||
|
||||
// preBindItem is item for lazy registering feature of router group. preBindItem is not really registered
|
||||
// to server when route function of the group called but is lazily registered when server starts.
|
||||
preBindItem struct {
|
||||
@ -104,16 +100,17 @@ func (d *Domain) Group(prefix string, groups ...func(group *RouterGroup)) *Route
|
||||
if prefix == "/" {
|
||||
prefix = ""
|
||||
}
|
||||
group := &RouterGroup{
|
||||
routerGroup := &RouterGroup{
|
||||
domain: d,
|
||||
server: d.server,
|
||||
prefix: prefix,
|
||||
}
|
||||
if len(groups) > 0 {
|
||||
for _, v := range groups {
|
||||
v(group)
|
||||
for _, nestedGroup := range groups {
|
||||
nestedGroup(routerGroup)
|
||||
}
|
||||
}
|
||||
return group
|
||||
return routerGroup
|
||||
}
|
||||
|
||||
// Group creates and returns a sub-group of current router group.
|
||||
@ -153,34 +150,26 @@ func (g *RouterGroup) Clone() *RouterGroup {
|
||||
}
|
||||
|
||||
// Bind does batch route registering feature for router group.
|
||||
func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup {
|
||||
func (g *RouterGroup) Bind(handlerOrObject ...interface{}) *RouterGroup {
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
group = g.Clone()
|
||||
)
|
||||
for _, item := range items {
|
||||
if len(item) < 3 {
|
||||
g.server.Logger().Fatalf(ctx, "invalid router item: %s", item)
|
||||
}
|
||||
bindType := gstr.ToUpper(gconv.String(item[0]))
|
||||
switch bindType {
|
||||
case groupBindTypeRest:
|
||||
group.preBindToLocalArray(groupBindTypeRest, gconv.String(item[0])+":"+gconv.String(item[1]), item[2])
|
||||
|
||||
case groupBindTypeMiddleware:
|
||||
group.preBindToLocalArray(groupBindTypeMiddleware, gconv.String(item[0])+":"+gconv.String(item[1]), item[2])
|
||||
for _, v := range handlerOrObject {
|
||||
var (
|
||||
item = v
|
||||
originValueAndKind = utils.OriginValueAndKind(item)
|
||||
)
|
||||
|
||||
switch originValueAndKind.OriginKind {
|
||||
case reflect.Func, reflect.Struct:
|
||||
group = group.preBindToLocalArray(
|
||||
groupBindTypeHandler,
|
||||
"/",
|
||||
item,
|
||||
)
|
||||
default:
|
||||
if strings.EqualFold(bindType, "ALL") {
|
||||
bindType = ""
|
||||
} else {
|
||||
bindType += ":"
|
||||
}
|
||||
if len(item) > 3 {
|
||||
group.preBindToLocalArray(groupBindTypeHandler, bindType+gconv.String(item[1]), item[2], item[3])
|
||||
} else {
|
||||
group.preBindToLocalArray(groupBindTypeHandler, bindType+gconv.String(item[1]), item[2])
|
||||
}
|
||||
g.server.Logger().Fatalf(ctx, "invalid bind parameter type: %v", originValueAndKind.InputValue.Type())
|
||||
}
|
||||
}
|
||||
return group
|
||||
@ -188,7 +177,12 @@ func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup {
|
||||
|
||||
// ALL registers a http handler to given route pattern and all http methods.
|
||||
func (g *RouterGroup) ALL(pattern string, object interface{}, params ...interface{}) *RouterGroup {
|
||||
return g.Clone().preBindToLocalArray(groupBindTypeHandler, defaultMethod+":"+pattern, object, params...)
|
||||
return g.Clone().preBindToLocalArray(
|
||||
groupBindTypeHandler,
|
||||
defaultMethod+":"+pattern,
|
||||
object,
|
||||
params...,
|
||||
)
|
||||
}
|
||||
|
||||
// ALLMap registers http handlers for http methods using map.
|
||||
@ -312,10 +306,10 @@ func (g *RouterGroup) doBindRoutersToServer(ctx context.Context, item *preBindIt
|
||||
domain = ""
|
||||
}
|
||||
if bindType == groupBindTypeRest {
|
||||
pattern = prefix + "/" + strings.TrimLeft(path, "/")
|
||||
pattern = path
|
||||
} else {
|
||||
pattern = g.server.serveHandlerKey(
|
||||
method, prefix+"/"+strings.TrimLeft(path, "/"), domain,
|
||||
method, path, domain,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -337,57 +331,95 @@ func (g *RouterGroup) doBindRoutersToServer(ctx context.Context, item *preBindIt
|
||||
g.server.Logger().Fatal(ctx, err.Error())
|
||||
return g
|
||||
}
|
||||
if g.server != nil {
|
||||
g.server.doBindHandler(ctx, pattern, funcInfo, g.middleware, source)
|
||||
in := doBindHandlerInput{
|
||||
Prefix: prefix,
|
||||
Pattern: pattern,
|
||||
FuncInfo: funcInfo,
|
||||
Middleware: g.middleware,
|
||||
Source: source,
|
||||
}
|
||||
if g.domain != nil {
|
||||
g.domain.doBindHandler(ctx, in)
|
||||
} else {
|
||||
g.domain.doBindHandler(ctx, pattern, funcInfo, g.middleware, source)
|
||||
g.server.doBindHandler(ctx, in)
|
||||
}
|
||||
} else {
|
||||
if len(extras) > 0 {
|
||||
if g.server != nil {
|
||||
if gstr.Contains(extras[0], ",") {
|
||||
g.server.doBindObject(
|
||||
ctx, pattern, object, extras[0], g.middleware, source,
|
||||
)
|
||||
if gstr.Contains(extras[0], ",") {
|
||||
in := doBindObjectInput{
|
||||
Prefix: prefix,
|
||||
Pattern: pattern,
|
||||
Object: object,
|
||||
Method: extras[0],
|
||||
Middleware: g.middleware,
|
||||
Source: source,
|
||||
}
|
||||
if g.domain != nil {
|
||||
g.domain.doBindObject(ctx, in)
|
||||
} else {
|
||||
g.server.doBindObjectMethod(
|
||||
ctx, pattern, object, extras[0], g.middleware, source,
|
||||
)
|
||||
g.server.doBindObject(ctx, in)
|
||||
}
|
||||
} else {
|
||||
if gstr.Contains(extras[0], ",") {
|
||||
g.domain.doBindObject(
|
||||
ctx, pattern, object, extras[0], g.middleware, source,
|
||||
)
|
||||
in := doBindObjectMethodInput{
|
||||
Prefix: prefix,
|
||||
Pattern: pattern,
|
||||
Object: object,
|
||||
Method: extras[0],
|
||||
Middleware: g.middleware,
|
||||
Source: source,
|
||||
}
|
||||
if g.domain != nil {
|
||||
g.domain.doBindObjectMethod(ctx, in)
|
||||
} else {
|
||||
g.domain.doBindObjectMethod(
|
||||
ctx, pattern, object, extras[0], g.middleware, source,
|
||||
)
|
||||
g.server.doBindObjectMethod(ctx, in)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
in := doBindObjectInput{
|
||||
Prefix: prefix,
|
||||
Pattern: pattern,
|
||||
Object: object,
|
||||
Method: "",
|
||||
Middleware: g.middleware,
|
||||
Source: source,
|
||||
}
|
||||
// At last, it treats the `object` as Object registering type.
|
||||
if g.server != nil {
|
||||
g.server.doBindObject(ctx, pattern, object, "", g.middleware, source)
|
||||
if g.domain != nil {
|
||||
g.domain.doBindObject(ctx, in)
|
||||
} else {
|
||||
g.domain.doBindObject(ctx, pattern, object, "", g.middleware, source)
|
||||
g.server.doBindObject(ctx, in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case groupBindTypeRest:
|
||||
if g.server != nil {
|
||||
g.server.doBindObjectRest(ctx, pattern, object, g.middleware, source)
|
||||
in := doBindObjectInput{
|
||||
Prefix: prefix,
|
||||
Pattern: pattern,
|
||||
Object: object,
|
||||
Method: "",
|
||||
Middleware: g.middleware,
|
||||
Source: source,
|
||||
}
|
||||
if g.domain != nil {
|
||||
g.domain.doBindObjectRest(ctx, in)
|
||||
} else {
|
||||
g.domain.doBindObjectRest(ctx, pattern, object, g.middleware, source)
|
||||
g.server.doBindObjectRest(ctx, in)
|
||||
}
|
||||
|
||||
case groupBindTypeHook:
|
||||
if h, ok := object.(HandlerFunc); ok {
|
||||
if g.server != nil {
|
||||
g.server.doBindHookHandler(ctx, pattern, extras[0], h, source)
|
||||
if handler, ok := object.(HandlerFunc); ok {
|
||||
in := doBindHookHandlerInput{
|
||||
Prefix: prefix,
|
||||
Pattern: pattern,
|
||||
HookName: extras[0],
|
||||
Handler: handler,
|
||||
Source: source,
|
||||
}
|
||||
if g.domain != nil {
|
||||
g.domain.doBindHookHandler(ctx, in)
|
||||
} else {
|
||||
g.domain.doBindHookHandler(ctx, pattern, extras[0], h, source)
|
||||
g.server.doBindHookHandler(ctx, in)
|
||||
}
|
||||
} else {
|
||||
g.server.Logger().Fatalf(ctx, "invalid hook handler for pattern: %s", pattern)
|
||||
|
@ -15,20 +15,41 @@ import (
|
||||
|
||||
// BindHookHandler registers handler for specified hook.
|
||||
func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) {
|
||||
s.doBindHookHandler(context.TODO(), pattern, hook, handler, "")
|
||||
s.doBindHookHandler(context.TODO(), doBindHookHandlerInput{
|
||||
Prefix: "",
|
||||
Pattern: pattern,
|
||||
HookName: hook,
|
||||
Handler: handler,
|
||||
Source: "",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) doBindHookHandler(ctx context.Context, pattern string, hook string, handler HandlerFunc, source string) {
|
||||
s.setHandler(ctx, pattern, &handlerItem{
|
||||
Type: HandlerTypeHook,
|
||||
Name: gdebug.FuncPath(handler),
|
||||
Info: handlerFuncInfo{
|
||||
Func: handler,
|
||||
Type: reflect.TypeOf(handler),
|
||||
type doBindHookHandlerInput struct {
|
||||
Prefix string
|
||||
Pattern string
|
||||
HookName string
|
||||
Handler HandlerFunc
|
||||
Source string
|
||||
}
|
||||
|
||||
func (s *Server) doBindHookHandler(ctx context.Context, in doBindHookHandlerInput) {
|
||||
s.setHandler(
|
||||
ctx,
|
||||
setHandlerInput{
|
||||
Prefix: in.Prefix,
|
||||
Pattern: in.Pattern,
|
||||
HandlerItem: &handlerItem{
|
||||
Type: HandlerTypeHook,
|
||||
Name: gdebug.FuncPath(in.Handler),
|
||||
Info: handlerFuncInfo{
|
||||
Func: in.Handler,
|
||||
Type: reflect.TypeOf(in.Handler),
|
||||
},
|
||||
HookName: in.HookName,
|
||||
Source: in.Source,
|
||||
},
|
||||
},
|
||||
HookName: hook,
|
||||
Source: source,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Server) BindHookHandlerByMap(pattern string, hookMap map[string]HandlerFunc) {
|
||||
|
@ -26,12 +26,16 @@ func (s *Server) BindMiddleware(pattern string, handlers ...HandlerFunc) {
|
||||
ctx = context.TODO()
|
||||
)
|
||||
for _, handler := range handlers {
|
||||
s.setHandler(ctx, pattern, &handlerItem{
|
||||
Type: HandlerTypeMiddleware,
|
||||
Name: gdebug.FuncPath(handler),
|
||||
Info: handlerFuncInfo{
|
||||
Func: handler,
|
||||
Type: reflect.TypeOf(handler),
|
||||
s.setHandler(ctx, setHandlerInput{
|
||||
Prefix: "",
|
||||
Pattern: pattern,
|
||||
HandlerItem: &handlerItem{
|
||||
Type: HandlerTypeMiddleware,
|
||||
Name: gdebug.FuncPath(handler),
|
||||
Info: handlerFuncInfo{
|
||||
Func: handler,
|
||||
Type: reflect.TypeOf(handler),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -45,12 +49,16 @@ func (s *Server) BindMiddlewareDefault(handlers ...HandlerFunc) {
|
||||
ctx = context.TODO()
|
||||
)
|
||||
for _, handler := range handlers {
|
||||
s.setHandler(ctx, defaultMiddlewarePattern, &handlerItem{
|
||||
Type: HandlerTypeMiddleware,
|
||||
Name: gdebug.FuncPath(handler),
|
||||
Info: handlerFuncInfo{
|
||||
Func: handler,
|
||||
Type: reflect.TypeOf(handler),
|
||||
s.setHandler(ctx, setHandlerInput{
|
||||
Prefix: "",
|
||||
Pattern: defaultMiddlewarePattern,
|
||||
HandlerItem: &handlerItem{
|
||||
Type: HandlerTypeMiddleware,
|
||||
Name: gdebug.FuncPath(handler),
|
||||
Info: handlerFuncInfo{
|
||||
Func: handler,
|
||||
Type: reflect.TypeOf(handler),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -19,9 +19,10 @@ import (
|
||||
)
|
||||
|
||||
// BindHandler registers a handler function to server with given pattern.
|
||||
// The parameter `handler` can be type of:
|
||||
// func(*ghttp.Request)
|
||||
// func(context.Context, Request)(Response, error)
|
||||
//
|
||||
// Note that the parameter `handler` can be type of:
|
||||
// 1. func(*ghttp.Request)
|
||||
// 2. func(context.Context, BizRequest)(BizResponse, error)
|
||||
func (s *Server) BindHandler(pattern string, handler interface{}) {
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
@ -30,26 +31,49 @@ func (s *Server) BindHandler(pattern string, handler interface{}) {
|
||||
if err != nil {
|
||||
s.Logger().Fatalf(ctx, `%+v`, err)
|
||||
}
|
||||
s.doBindHandler(ctx, pattern, funcInfo, nil, "")
|
||||
s.doBindHandler(ctx, doBindHandlerInput{
|
||||
Prefix: "",
|
||||
Pattern: pattern,
|
||||
FuncInfo: funcInfo,
|
||||
Middleware: nil,
|
||||
Source: "",
|
||||
})
|
||||
}
|
||||
|
||||
type doBindHandlerInput struct {
|
||||
Prefix string
|
||||
Pattern string
|
||||
FuncInfo handlerFuncInfo
|
||||
Middleware []HandlerFunc
|
||||
Source string
|
||||
}
|
||||
|
||||
// doBindHandler registers a handler function to server with given pattern.
|
||||
//
|
||||
// The parameter `pattern` is like:
|
||||
// /user/list, put:/user, delete:/user, post:/user@goframe.org
|
||||
func (s *Server) doBindHandler(ctx context.Context, pattern string, funcInfo handlerFuncInfo, middleware []HandlerFunc, source string) {
|
||||
s.setHandler(ctx, pattern, &handlerItem{
|
||||
Name: gdebug.FuncPath(funcInfo.Func),
|
||||
Type: HandlerTypeHandler,
|
||||
Info: funcInfo,
|
||||
Middleware: middleware,
|
||||
Source: source,
|
||||
func (s *Server) doBindHandler(ctx context.Context, in doBindHandlerInput) {
|
||||
s.setHandler(ctx, setHandlerInput{
|
||||
Prefix: in.Prefix,
|
||||
Pattern: in.Pattern,
|
||||
HandlerItem: &handlerItem{
|
||||
Name: gdebug.FuncPath(in.FuncInfo.Func),
|
||||
Type: HandlerTypeHandler,
|
||||
Info: in.FuncInfo,
|
||||
Middleware: in.Middleware,
|
||||
Source: in.Source,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// bindHandlerByMap registers handlers to server using map.
|
||||
func (s *Server) bindHandlerByMap(ctx context.Context, m map[string]*handlerItem) {
|
||||
for p, h := range m {
|
||||
s.setHandler(ctx, p, h)
|
||||
func (s *Server) bindHandlerByMap(ctx context.Context, prefix string, m map[string]*handlerItem) {
|
||||
for pattern, handler := range m {
|
||||
s.setHandler(ctx, setHandlerInput{
|
||||
Prefix: prefix,
|
||||
Pattern: pattern,
|
||||
HandlerItem: handler,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,6 +186,7 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth
|
||||
return
|
||||
}
|
||||
|
||||
// The request struct should be named as `xxxReq`.
|
||||
if !gstr.HasSuffix(reflectType.In(1).String(), `Req`) {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
@ -171,6 +196,7 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth
|
||||
return
|
||||
}
|
||||
|
||||
// The response struct should be named as `xxxRes`.
|
||||
if !gstr.HasSuffix(reflectType.Out(0).String(), `Res`) {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
|
@ -21,8 +21,6 @@ import (
|
||||
//
|
||||
// The optional parameter `method` is used to specify the method to be registered, which
|
||||
// supports multiple method names, multiple methods are separated by char ',', case-sensitive.
|
||||
//
|
||||
// Note that the route method should be defined as ghttp.HandlerFunc.
|
||||
func (s *Server) BindObject(pattern string, object interface{}, method ...string) {
|
||||
var (
|
||||
bindMethod = ""
|
||||
@ -30,97 +28,124 @@ func (s *Server) BindObject(pattern string, object interface{}, method ...string
|
||||
if len(method) > 0 {
|
||||
bindMethod = method[0]
|
||||
}
|
||||
s.doBindObject(context.TODO(), pattern, object, bindMethod, nil, "")
|
||||
s.doBindObject(context.TODO(), doBindObjectInput{
|
||||
Prefix: "",
|
||||
Pattern: pattern,
|
||||
Object: object,
|
||||
Method: bindMethod,
|
||||
Middleware: nil,
|
||||
Source: "",
|
||||
})
|
||||
}
|
||||
|
||||
// BindObjectMethod registers specified method of object to server routes with given pattern.
|
||||
//
|
||||
// The optional parameter `method` is used to specify the method to be registered, which
|
||||
// does not supports multiple method names but only one, case-sensitive.
|
||||
//
|
||||
// Note that the route method should be defined as ghttp.HandlerFunc.
|
||||
func (s *Server) BindObjectMethod(pattern string, object interface{}, method string) {
|
||||
s.doBindObjectMethod(context.TODO(), pattern, object, method, nil, "")
|
||||
s.doBindObjectMethod(context.TODO(), doBindObjectMethodInput{
|
||||
Prefix: "",
|
||||
Pattern: pattern,
|
||||
Object: object,
|
||||
Method: method,
|
||||
Middleware: nil,
|
||||
Source: "",
|
||||
})
|
||||
}
|
||||
|
||||
// BindObjectRest registers object in REST API styles to server with specified pattern.
|
||||
// Note that the route method should be defined as ghttp.HandlerFunc.
|
||||
func (s *Server) BindObjectRest(pattern string, object interface{}) {
|
||||
s.doBindObjectRest(context.TODO(), pattern, object, nil, "")
|
||||
s.doBindObjectRest(context.TODO(), doBindObjectInput{
|
||||
Prefix: "",
|
||||
Pattern: pattern,
|
||||
Object: object,
|
||||
Method: "",
|
||||
Middleware: nil,
|
||||
Source: "",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) doBindObject(ctx context.Context, pattern string, object interface{}, method string, middleware []HandlerFunc, source string) {
|
||||
type doBindObjectInput struct {
|
||||
Prefix string
|
||||
Pattern string
|
||||
Object interface{}
|
||||
Method string
|
||||
Middleware []HandlerFunc
|
||||
Source string
|
||||
}
|
||||
|
||||
func (s *Server) doBindObject(ctx context.Context, in doBindObjectInput) {
|
||||
// Convert input method to map for convenience and high performance searching purpose.
|
||||
var (
|
||||
methodMap map[string]bool
|
||||
)
|
||||
if len(method) > 0 {
|
||||
if len(in.Method) > 0 {
|
||||
methodMap = make(map[string]bool)
|
||||
for _, v := range strings.Split(method, ",") {
|
||||
for _, v := range strings.Split(in.Method, ",") {
|
||||
methodMap[strings.TrimSpace(v)] = true
|
||||
}
|
||||
}
|
||||
// If the `method` in `pattern` is `defaultMethod`,
|
||||
// it removes for convenience for next statement control.
|
||||
domain, method, path, err := s.parsePattern(pattern)
|
||||
domain, method, path, err := s.parsePattern(in.Pattern)
|
||||
if err != nil {
|
||||
s.Logger().Fatalf(ctx, `%+v`, err)
|
||||
return
|
||||
}
|
||||
if strings.EqualFold(method, defaultMethod) {
|
||||
pattern = s.serveHandlerKey("", path, domain)
|
||||
in.Pattern = s.serveHandlerKey("", path, domain)
|
||||
}
|
||||
var (
|
||||
m = make(map[string]*handlerItem)
|
||||
v = reflect.ValueOf(object)
|
||||
t = v.Type()
|
||||
initFunc func(*Request)
|
||||
shutFunc func(*Request)
|
||||
handlerMap = make(map[string]*handlerItem)
|
||||
reflectValue = reflect.ValueOf(in.Object)
|
||||
reflectType = reflectValue.Type()
|
||||
initFunc func(*Request)
|
||||
shutFunc func(*Request)
|
||||
)
|
||||
// If given `object` is not pointer, it then creates a temporary one,
|
||||
// of which the value is `v`.
|
||||
if v.Kind() == reflect.Struct {
|
||||
newValue := reflect.New(t)
|
||||
newValue.Elem().Set(v)
|
||||
v = newValue
|
||||
t = v.Type()
|
||||
if reflectValue.Kind() == reflect.Struct {
|
||||
newValue := reflect.New(reflectType)
|
||||
newValue.Elem().Set(reflectValue)
|
||||
reflectValue = newValue
|
||||
reflectType = reflectValue.Type()
|
||||
}
|
||||
structName := t.Elem().Name()
|
||||
if v.MethodByName("Init").IsValid() {
|
||||
initFunc = v.MethodByName("Init").Interface().(func(*Request))
|
||||
structName := reflectType.Elem().Name()
|
||||
if reflectValue.MethodByName("Init").IsValid() {
|
||||
initFunc = reflectValue.MethodByName("Init").Interface().(func(*Request))
|
||||
}
|
||||
if v.MethodByName("Shut").IsValid() {
|
||||
shutFunc = v.MethodByName("Shut").Interface().(func(*Request))
|
||||
if reflectValue.MethodByName("Shut").IsValid() {
|
||||
shutFunc = reflectValue.MethodByName("Shut").Interface().(func(*Request))
|
||||
}
|
||||
pkgPath := t.Elem().PkgPath()
|
||||
pkgPath := reflectType.Elem().PkgPath()
|
||||
pkgName := gfile.Basename(pkgPath)
|
||||
for i := 0; i < v.NumMethod(); i++ {
|
||||
methodName := t.Method(i).Name
|
||||
for i := 0; i < reflectValue.NumMethod(); i++ {
|
||||
methodName := reflectType.Method(i).Name
|
||||
if methodMap != nil && !methodMap[methodName] {
|
||||
continue
|
||||
}
|
||||
if methodName == "Init" || methodName == "Shut" {
|
||||
continue
|
||||
}
|
||||
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
|
||||
objName := gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "")
|
||||
if objName[0] == '*' {
|
||||
objName = fmt.Sprintf(`(%s)`, objName)
|
||||
}
|
||||
|
||||
funcInfo, err := s.checkAndCreateFuncInfo(v.Method(i).Interface(), pkgPath, objName, methodName)
|
||||
funcInfo, err := s.checkAndCreateFuncInfo(reflectValue.Method(i).Interface(), pkgPath, objName, methodName)
|
||||
if err != nil {
|
||||
s.Logger().Fatalf(ctx, `%+v`, err)
|
||||
}
|
||||
|
||||
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, true)
|
||||
m[key] = &handlerItem{
|
||||
key := s.mergeBuildInNameToPattern(in.Pattern, structName, methodName, true)
|
||||
handlerMap[key] = &handlerItem{
|
||||
Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
|
||||
Type: HandlerTypeObject,
|
||||
Info: funcInfo,
|
||||
InitFunc: initFunc,
|
||||
ShutFunc: shutFunc,
|
||||
Middleware: middleware,
|
||||
Source: source,
|
||||
Middleware: in.Middleware,
|
||||
Source: in.Source,
|
||||
}
|
||||
// If there's "Index" method, then an additional route is automatically added
|
||||
// to match the main URI, for example:
|
||||
@ -129,61 +154,70 @@ func (s *Server) doBindObject(ctx context.Context, pattern string, object interf
|
||||
//
|
||||
// Note that if there's built-in variables in pattern, this route will not be added
|
||||
// automatically.
|
||||
if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
|
||||
if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, in.Pattern) {
|
||||
p := gstr.PosRI(key, "/index")
|
||||
k := key[0:p] + key[p+6:]
|
||||
if len(k) == 0 || k[0] == '@' {
|
||||
k = "/" + k
|
||||
}
|
||||
m[k] = &handlerItem{
|
||||
handlerMap[k] = &handlerItem{
|
||||
Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
|
||||
Type: HandlerTypeObject,
|
||||
Info: funcInfo,
|
||||
InitFunc: initFunc,
|
||||
ShutFunc: shutFunc,
|
||||
Middleware: middleware,
|
||||
Source: source,
|
||||
Middleware: in.Middleware,
|
||||
Source: in.Source,
|
||||
}
|
||||
}
|
||||
}
|
||||
s.bindHandlerByMap(ctx, m)
|
||||
s.bindHandlerByMap(ctx, in.Prefix, handlerMap)
|
||||
}
|
||||
|
||||
func (s *Server) doBindObjectMethod(ctx context.Context, pattern string, object interface{}, method string, middleware []HandlerFunc, source string) {
|
||||
type doBindObjectMethodInput struct {
|
||||
Prefix string
|
||||
Pattern string
|
||||
Object interface{}
|
||||
Method string
|
||||
Middleware []HandlerFunc
|
||||
Source string
|
||||
}
|
||||
|
||||
func (s *Server) doBindObjectMethod(ctx context.Context, in doBindObjectMethodInput) {
|
||||
var (
|
||||
m = make(map[string]*handlerItem)
|
||||
v = reflect.ValueOf(object)
|
||||
t = v.Type()
|
||||
initFunc func(*Request)
|
||||
shutFunc func(*Request)
|
||||
handlerMap = make(map[string]*handlerItem)
|
||||
reflectValue = reflect.ValueOf(in.Object)
|
||||
reflectType = reflectValue.Type()
|
||||
initFunc func(*Request)
|
||||
shutFunc func(*Request)
|
||||
)
|
||||
// If given `object` is not pointer, it then creates a temporary one,
|
||||
// of which the value is `v`.
|
||||
if v.Kind() == reflect.Struct {
|
||||
newValue := reflect.New(t)
|
||||
newValue.Elem().Set(v)
|
||||
v = newValue
|
||||
t = v.Type()
|
||||
if reflectValue.Kind() == reflect.Struct {
|
||||
newValue := reflect.New(reflectType)
|
||||
newValue.Elem().Set(reflectValue)
|
||||
reflectValue = newValue
|
||||
reflectType = reflectValue.Type()
|
||||
}
|
||||
var (
|
||||
structName = t.Elem().Name()
|
||||
methodName = strings.TrimSpace(method)
|
||||
methodValue = v.MethodByName(methodName)
|
||||
structName = reflectType.Elem().Name()
|
||||
methodName = strings.TrimSpace(in.Method)
|
||||
methodValue = reflectValue.MethodByName(methodName)
|
||||
)
|
||||
if !methodValue.IsValid() {
|
||||
s.Logger().Fatalf(ctx, "invalid method name: %s", methodName)
|
||||
return
|
||||
}
|
||||
if v.MethodByName("Init").IsValid() {
|
||||
initFunc = v.MethodByName("Init").Interface().(func(*Request))
|
||||
if reflectValue.MethodByName("Init").IsValid() {
|
||||
initFunc = reflectValue.MethodByName("Init").Interface().(func(*Request))
|
||||
}
|
||||
if v.MethodByName("Shut").IsValid() {
|
||||
shutFunc = v.MethodByName("Shut").Interface().(func(*Request))
|
||||
if reflectValue.MethodByName("Shut").IsValid() {
|
||||
shutFunc = reflectValue.MethodByName("Shut").Interface().(func(*Request))
|
||||
}
|
||||
var (
|
||||
pkgPath = t.Elem().PkgPath()
|
||||
pkgPath = reflectType.Elem().PkgPath()
|
||||
pkgName = gfile.Basename(pkgPath)
|
||||
objName = gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
|
||||
objName = gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "")
|
||||
)
|
||||
if objName[0] == '*' {
|
||||
objName = fmt.Sprintf(`(%s)`, objName)
|
||||
@ -194,70 +228,75 @@ func (s *Server) doBindObjectMethod(ctx context.Context, pattern string, object
|
||||
s.Logger().Fatalf(ctx, `%+v`, err)
|
||||
}
|
||||
|
||||
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, false)
|
||||
m[key] = &handlerItem{
|
||||
key := s.mergeBuildInNameToPattern(in.Pattern, structName, methodName, false)
|
||||
handlerMap[key] = &handlerItem{
|
||||
Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
|
||||
Type: HandlerTypeObject,
|
||||
Info: funcInfo,
|
||||
InitFunc: initFunc,
|
||||
ShutFunc: shutFunc,
|
||||
Middleware: middleware,
|
||||
Source: source,
|
||||
Middleware: in.Middleware,
|
||||
Source: in.Source,
|
||||
}
|
||||
|
||||
s.bindHandlerByMap(ctx, m)
|
||||
s.bindHandlerByMap(ctx, in.Prefix, handlerMap)
|
||||
}
|
||||
|
||||
func (s *Server) doBindObjectRest(ctx context.Context, pattern string, object interface{}, middleware []HandlerFunc, source string) {
|
||||
func (s *Server) doBindObjectRest(ctx context.Context, in doBindObjectInput) {
|
||||
var (
|
||||
m = make(map[string]*handlerItem)
|
||||
v = reflect.ValueOf(object)
|
||||
t = v.Type()
|
||||
initFunc func(*Request)
|
||||
shutFunc func(*Request)
|
||||
handlerMap = make(map[string]*handlerItem)
|
||||
reflectValue = reflect.ValueOf(in.Object)
|
||||
reflectType = reflectValue.Type()
|
||||
initFunc func(*Request)
|
||||
shutFunc func(*Request)
|
||||
)
|
||||
// If given `object` is not pointer, it then creates a temporary one,
|
||||
// of which the value is `v`.
|
||||
if v.Kind() == reflect.Struct {
|
||||
newValue := reflect.New(t)
|
||||
newValue.Elem().Set(v)
|
||||
v = newValue
|
||||
t = v.Type()
|
||||
if reflectValue.Kind() == reflect.Struct {
|
||||
newValue := reflect.New(reflectType)
|
||||
newValue.Elem().Set(reflectValue)
|
||||
reflectValue = newValue
|
||||
reflectType = reflectValue.Type()
|
||||
}
|
||||
structName := t.Elem().Name()
|
||||
if v.MethodByName(methodNameInit).IsValid() {
|
||||
initFunc = v.MethodByName(methodNameInit).Interface().(func(*Request))
|
||||
structName := reflectType.Elem().Name()
|
||||
if reflectValue.MethodByName(methodNameInit).IsValid() {
|
||||
initFunc = reflectValue.MethodByName(methodNameInit).Interface().(func(*Request))
|
||||
}
|
||||
if v.MethodByName(methodNameShut).IsValid() {
|
||||
shutFunc = v.MethodByName(methodNameShut).Interface().(func(*Request))
|
||||
if reflectValue.MethodByName(methodNameShut).IsValid() {
|
||||
shutFunc = reflectValue.MethodByName(methodNameShut).Interface().(func(*Request))
|
||||
}
|
||||
pkgPath := t.Elem().PkgPath()
|
||||
for i := 0; i < v.NumMethod(); i++ {
|
||||
methodName := t.Method(i).Name
|
||||
pkgPath := reflectType.Elem().PkgPath()
|
||||
for i := 0; i < reflectValue.NumMethod(); i++ {
|
||||
methodName := reflectType.Method(i).Name
|
||||
if _, ok := methodsMap[strings.ToUpper(methodName)]; !ok {
|
||||
continue
|
||||
}
|
||||
pkgName := gfile.Basename(pkgPath)
|
||||
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
|
||||
objName := gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "")
|
||||
if objName[0] == '*' {
|
||||
objName = fmt.Sprintf(`(%s)`, objName)
|
||||
}
|
||||
|
||||
funcInfo, err := s.checkAndCreateFuncInfo(v.Method(i).Interface(), pkgPath, objName, methodName)
|
||||
funcInfo, err := s.checkAndCreateFuncInfo(
|
||||
reflectValue.Method(i).Interface(),
|
||||
pkgPath,
|
||||
objName,
|
||||
methodName,
|
||||
)
|
||||
if err != nil {
|
||||
s.Logger().Fatalf(ctx, `%+v`, err)
|
||||
}
|
||||
|
||||
key := s.mergeBuildInNameToPattern(methodName+":"+pattern, structName, methodName, false)
|
||||
m[key] = &handlerItem{
|
||||
key := s.mergeBuildInNameToPattern(methodName+":"+in.Pattern, structName, methodName, false)
|
||||
handlerMap[key] = &handlerItem{
|
||||
Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
|
||||
Type: HandlerTypeObject,
|
||||
Info: funcInfo,
|
||||
InitFunc: initFunc,
|
||||
ShutFunc: shutFunc,
|
||||
Middleware: middleware,
|
||||
Source: source,
|
||||
Middleware: in.Middleware,
|
||||
Source: in.Source,
|
||||
}
|
||||
}
|
||||
s.bindHandlerByMap(ctx, m)
|
||||
s.bindHandlerByMap(ctx, in.Prefix, handlerMap)
|
||||
}
|
||||
|
@ -333,16 +333,16 @@ func Test_Router_DomainGroup(t *testing.T) {
|
||||
s := g.Server(p)
|
||||
d := s.Domain("localhost, local")
|
||||
d.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Group("/app", func(gApp *ghttp.RouterGroup) {
|
||||
gApp.GET("/{table}/list/{page}.html", func(r *ghttp.Request) {
|
||||
group.Group("/app", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/{table}/list/{page}.html", func(r *ghttp.Request) {
|
||||
intlog.Print(r.Context(), "/{table}/list/{page}.html")
|
||||
r.Response.Write(r.Get("table"), "&", r.Get("page"))
|
||||
})
|
||||
gApp.GET("/order/info/{order_id}", func(r *ghttp.Request) {
|
||||
group.GET("/order/info/{order_id}", func(r *ghttp.Request) {
|
||||
intlog.Print(r.Context(), "/order/info/{order_id}")
|
||||
r.Response.Write(r.Get("order_id"))
|
||||
})
|
||||
gApp.DELETE("/comment/{id}", func(r *ghttp.Request) {
|
||||
group.DELETE("/comment/{id}", func(r *ghttp.Request) {
|
||||
intlog.Print(r.Context(), "/comment/{id}")
|
||||
r.Response.Write(r.Get("id"))
|
||||
})
|
||||
|
@ -74,33 +74,3 @@ func Test_Router_Group_Hook2(t *testing.T) {
|
||||
t.Assert(client.PostContent(ctx, "/api/ThisDoesNotExist"), "Not Found")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Group_Hook3(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.Group("/api").Bind([]g.Slice{
|
||||
{"ALL", "handler", func(r *ghttp.Request) {
|
||||
r.Response.Write("1")
|
||||
}},
|
||||
{"ALL", "/*", func(r *ghttp.Request) {
|
||||
r.Response.Write("0")
|
||||
}, ghttp.HookBeforeServe},
|
||||
{"ALL", "/*", func(r *ghttp.Request) {
|
||||
r.Response.Write("2")
|
||||
}, ghttp.HookAfterServe},
|
||||
})
|
||||
|
||||
s.SetPort(p)
|
||||
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", p))
|
||||
t.Assert(client.GetContent(ctx, "/api/handler"), "012")
|
||||
t.Assert(client.PostContent(ctx, "/api/handler"), "012")
|
||||
t.Assert(client.DeleteContent(ctx, "/api/ThisDoesNotExist"), "02")
|
||||
})
|
||||
}
|
||||
|
@ -78,39 +78,6 @@ func Test_Router_GroupBasic1(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_GroupBasic2(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
obj := new(GroupObject)
|
||||
// 分组路由批量注册
|
||||
s.Group("/api").Bind([]g.Slice{
|
||||
{"ALL", "/handler", Handler},
|
||||
{"ALL", "/obj", obj},
|
||||
{"GET", "/obj/my-show", obj, "Show"},
|
||||
{"REST", "/obj/rest", obj},
|
||||
})
|
||||
s.SetPort(p)
|
||||
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", p))
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/api/handler"), "Handler")
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/api/obj/delete"), "1Object Delete2")
|
||||
t.Assert(client.GetContent(ctx, "/api/obj/my-show"), "1Object Show2")
|
||||
t.Assert(client.GetContent(ctx, "/api/obj/show"), "1Object Show2")
|
||||
t.Assert(client.DeleteContent(ctx, "/api/obj/rest"), "1Object Delete2")
|
||||
|
||||
t.Assert(client.DeleteContent(ctx, "/ThisDoesNotExist"), "Not Found")
|
||||
t.Assert(client.DeleteContent(ctx, "/api/ThisDoesNotExist"), "Not Found")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_GroupBuildInVar(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
|
@ -59,3 +59,139 @@ func Test_Router_Handler_Extended_Handler_WithObject(t *testing.T) {
|
||||
t.Assert(client.GetContent(ctx, "/test/error"), `{"code":50,"message":"error","data":null}`)
|
||||
})
|
||||
}
|
||||
|
||||
type TestForHandlerWithObjectAndMeta1Req struct {
|
||||
g.Meta `path:"/custom-test1" method:"get"`
|
||||
Age int
|
||||
Name string
|
||||
}
|
||||
type TestForHandlerWithObjectAndMeta1Res struct {
|
||||
Id int
|
||||
Age int
|
||||
}
|
||||
|
||||
type TestForHandlerWithObjectAndMeta2Req struct {
|
||||
g.Meta `path:"/custom-test2" method:"get"`
|
||||
Age int
|
||||
Name string
|
||||
}
|
||||
type TestForHandlerWithObjectAndMeta2Res struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
type ControllerForHandlerWithObjectAndMeta1 struct{}
|
||||
|
||||
func (ControllerForHandlerWithObjectAndMeta1) Test1(ctx context.Context, req *TestForHandlerWithObjectAndMeta1Req) (res *TestForHandlerWithObjectAndMeta1Res, err error) {
|
||||
return &TestForHandlerWithObjectAndMeta1Res{
|
||||
Id: 1,
|
||||
Age: req.Age,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ControllerForHandlerWithObjectAndMeta1) Test2(ctx context.Context, req *TestForHandlerWithObjectAndMeta2Req) (res *TestForHandlerWithObjectAndMeta2Res, err error) {
|
||||
return &TestForHandlerWithObjectAndMeta2Res{
|
||||
Id: 1,
|
||||
Name: req.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type TestForHandlerWithObjectAndMeta3Req struct {
|
||||
g.Meta `path:"/custom-test3" method:"get"`
|
||||
Age int
|
||||
Name string
|
||||
}
|
||||
type TestForHandlerWithObjectAndMeta3Res struct {
|
||||
Id int
|
||||
Age int
|
||||
}
|
||||
|
||||
type TestForHandlerWithObjectAndMeta4Req struct {
|
||||
g.Meta `path:"/custom-test4" method:"get"`
|
||||
Age int
|
||||
Name string
|
||||
}
|
||||
type TestForHandlerWithObjectAndMeta4Res struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
type ControllerForHandlerWithObjectAndMeta2 struct{}
|
||||
|
||||
func (ControllerForHandlerWithObjectAndMeta2) Test3(ctx context.Context, req *TestForHandlerWithObjectAndMeta3Req) (res *TestForHandlerWithObjectAndMeta3Res, err error) {
|
||||
return &TestForHandlerWithObjectAndMeta3Res{
|
||||
Id: 1,
|
||||
Age: req.Age,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ControllerForHandlerWithObjectAndMeta2) Test4(ctx context.Context, req *TestForHandlerWithObjectAndMeta4Req) (res *TestForHandlerWithObjectAndMeta4Res, err error) {
|
||||
return &TestForHandlerWithObjectAndMeta4Res{
|
||||
Id: 1,
|
||||
Name: req.Name,
|
||||
}, nil
|
||||
}
|
||||
func Test_Router_Handler_Extended_Handler_WithObjectAndMeta(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.Use(ghttp.MiddlewareHandlerResponse)
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/", new(ControllerForHandlerWithObjectAndMeta1))
|
||||
})
|
||||
s.SetPort(p)
|
||||
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", p))
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/"), `{"code":0,"message":"","data":null}`)
|
||||
t.Assert(client.GetContent(ctx, "/custom-test1?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`)
|
||||
t.Assert(client.GetContent(ctx, "/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
|
||||
t.Assert(client.PostContent(ctx, "/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":null}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Handler_Extended_Handler_Group_Bind(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.Use(ghttp.MiddlewareHandlerResponse)
|
||||
s.Group("/api/v1", func(group *ghttp.RouterGroup) {
|
||||
group.Bind(
|
||||
new(ControllerForHandlerWithObjectAndMeta1),
|
||||
new(ControllerForHandlerWithObjectAndMeta2),
|
||||
)
|
||||
})
|
||||
s.Group("/api/v2", func(group *ghttp.RouterGroup) {
|
||||
group.Bind(
|
||||
new(ControllerForHandlerWithObjectAndMeta1),
|
||||
new(ControllerForHandlerWithObjectAndMeta2),
|
||||
)
|
||||
})
|
||||
s.SetPort(p)
|
||||
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", p))
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/"), `{"code":0,"message":"","data":null}`)
|
||||
t.Assert(client.GetContent(ctx, "/api/v1/custom-test1?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`)
|
||||
t.Assert(client.GetContent(ctx, "/api/v1/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
|
||||
t.Assert(client.PostContent(ctx, "/api/v1/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":null}`)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/api/v1/custom-test3?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`)
|
||||
t.Assert(client.GetContent(ctx, "/api/v1/custom-test4?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
|
||||
|
||||
t.Assert(client.GetContent(ctx, "/api/v2/custom-test1?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`)
|
||||
t.Assert(client.GetContent(ctx, "/api/v2/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
|
||||
t.Assert(client.GetContent(ctx, "/api/v2/custom-test3?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`)
|
||||
t.Assert(client.GetContent(ctx, "/api/v2/custom-test4?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
|
||||
})
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ func ExampleCache_UpdateExpire() {
|
||||
expire1, _ := c.GetExpire(ctx, "k1")
|
||||
fmt.Println(expire1)
|
||||
|
||||
// Output:
|
||||
// May Output:
|
||||
// 1s
|
||||
// 500ms
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ func TestCache_UpdateExpire(t *testing.T) {
|
||||
newExpire := 10 * time.Second
|
||||
oldExpire2, err := cache.UpdateExpire(ctx, 1, newExpire)
|
||||
t.AssertNil(err)
|
||||
t.Assert(oldExpire2, oldExpire)
|
||||
t.AssertIN(oldExpire2, g.Slice{oldExpire, `2.999s`})
|
||||
|
||||
e, _ := cache.GetExpire(ctx, 1)
|
||||
t.AssertNE(e, oldExpire)
|
||||
|
@ -82,6 +82,7 @@ const (
|
||||
TagNameMethod = `method`
|
||||
TagNameMime = `mime`
|
||||
TagNameType = `type`
|
||||
TagNameDomain = `domain`
|
||||
TagNameValidate = `v`
|
||||
)
|
||||
|
||||
|
@ -327,14 +327,7 @@ func Split(str, delimiter string) []string {
|
||||
// and calls Trim to every element of this array. It ignores the elements
|
||||
// which are empty after Trim.
|
||||
func SplitAndTrim(str, delimiter string, characterMask ...string) []string {
|
||||
array := make([]string, 0)
|
||||
for _, v := range strings.Split(str, delimiter) {
|
||||
v = Trim(v, characterMask...)
|
||||
if v != "" {
|
||||
array = append(array, v)
|
||||
}
|
||||
}
|
||||
return array
|
||||
return utils.SplitAndTrim(str, delimiter, characterMask...)
|
||||
}
|
||||
|
||||
// Join concatenates the elements of `array` to create a single string. The separator string
|
||||
|
@ -7,8 +7,11 @@
|
||||
package gconv
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/structs"
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -38,7 +41,18 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
|
||||
}
|
||||
pointerKind = pointerType.Kind()
|
||||
if pointerKind != reflect.Ptr {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "params should be type of pointer, but got type: %v", pointerKind)
|
||||
if pointerValue.CanAddr() {
|
||||
pointerValue = pointerValue.Addr()
|
||||
pointerType = pointerValue.Type()
|
||||
pointerKind = pointerType.Kind()
|
||||
} else {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"params should be type of pointer, but got type: %v",
|
||||
pointerType,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
// Direct assignment checks!
|
||||
var (
|
||||
@ -94,3 +108,410 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
|
||||
return doStruct(params, pointer, keyToAttributeNameMapping, "")
|
||||
}
|
||||
}
|
||||
|
||||
// ScanList converts `structSlice` to struct slice which contains other complex struct attributes.
|
||||
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
|
||||
//
|
||||
// Usage example 1: Normal attribute struct relation:
|
||||
// type EntityUser struct {
|
||||
// Uid int
|
||||
// Name string
|
||||
// }
|
||||
// type EntityUserDetail struct {
|
||||
// Uid int
|
||||
// Address string
|
||||
// }
|
||||
// type EntityUserScores struct {
|
||||
// Id int
|
||||
// Uid int
|
||||
// Score int
|
||||
// Course string
|
||||
// }
|
||||
// type Entity struct {
|
||||
// User *EntityUser
|
||||
// UserDetail *EntityUserDetail
|
||||
// UserScores []*EntityUserScores
|
||||
// }
|
||||
// var users []*Entity
|
||||
// ScanList(records, &users, "User")
|
||||
// ScanList(records, &users, "User", "uid")
|
||||
// ScanList(records, &users, "UserDetail", "User", "uid:Uid")
|
||||
// ScanList(records, &users, "UserScores", "User", "uid:Uid")
|
||||
// ScanList(records, &users, "UserScores", "User", "uid")
|
||||
//
|
||||
//
|
||||
// Usage example 2: Embedded attribute struct relation:
|
||||
// type EntityUser struct {
|
||||
// Uid int
|
||||
// Name string
|
||||
// }
|
||||
// type EntityUserDetail struct {
|
||||
// Uid int
|
||||
// Address string
|
||||
// }
|
||||
// type EntityUserScores struct {
|
||||
// Id int
|
||||
// Uid int
|
||||
// Score int
|
||||
// }
|
||||
// type Entity struct {
|
||||
// EntityUser
|
||||
// UserDetail EntityUserDetail
|
||||
// UserScores []EntityUserScores
|
||||
// }
|
||||
//
|
||||
// var users []*Entity
|
||||
// ScanList(records, &users)
|
||||
// ScanList(records, &users, "UserDetail", "uid")
|
||||
// ScanList(records, &users, "UserScores", "uid")
|
||||
//
|
||||
//
|
||||
// The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct
|
||||
// that current result will be bound to.
|
||||
//
|
||||
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
|
||||
// struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
|
||||
// name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
|
||||
// given `relation` parameter.
|
||||
//
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
|
||||
var (
|
||||
relationAttrName string
|
||||
relationFields string
|
||||
)
|
||||
switch len(relationAttrNameAndFields) {
|
||||
case 2:
|
||||
relationAttrName = relationAttrNameAndFields[0]
|
||||
relationFields = relationAttrNameAndFields[1]
|
||||
case 1:
|
||||
relationFields = relationAttrNameAndFields[0]
|
||||
}
|
||||
return doScanList(structSlice, structSlicePointer, bindToAttrName, relationAttrName, relationFields)
|
||||
}
|
||||
|
||||
// doScanList converts `structSlice` to struct slice which contains other complex struct attributes recursively.
|
||||
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
|
||||
func doScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName, relationAttrName, relationFields string) (err error) {
|
||||
var (
|
||||
maps = Maps(structSlice)
|
||||
)
|
||||
if len(maps) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Necessary checks for parameters.
|
||||
if bindToAttrName == "" {
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
|
||||
}
|
||||
|
||||
if relationAttrName == "." {
|
||||
relationAttrName = ""
|
||||
}
|
||||
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(structSlicePointer)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
if reflectKind == reflect.Interface {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
if reflectKind != reflect.Ptr {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
|
||||
reflectKind,
|
||||
)
|
||||
}
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
|
||||
reflectKind,
|
||||
)
|
||||
}
|
||||
length := len(maps)
|
||||
if length == 0 {
|
||||
// The pointed slice is not empty.
|
||||
if reflectValue.Len() > 0 {
|
||||
// It here checks if it has struct item, which is already initialized.
|
||||
// It then returns error to warn the developer its empty and no conversion.
|
||||
if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
}
|
||||
// Do nothing for empty struct slice.
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
arrayValue reflect.Value // Like: []*Entity
|
||||
arrayItemType reflect.Type // Like: *Entity
|
||||
reflectType = reflect.TypeOf(structSlicePointer)
|
||||
)
|
||||
if reflectValue.Len() > 0 {
|
||||
arrayValue = reflectValue
|
||||
} else {
|
||||
arrayValue = reflect.MakeSlice(reflectType.Elem(), length, length)
|
||||
}
|
||||
|
||||
// Slice element item.
|
||||
arrayItemType = arrayValue.Index(0).Type()
|
||||
|
||||
// Relation variables.
|
||||
var (
|
||||
relationDataMap map[string]interface{}
|
||||
relationFromFieldName string // Eg: relationKV: id:uid -> id
|
||||
relationBindToFieldName string // Eg: relationKV: id:uid -> uid
|
||||
)
|
||||
if len(relationFields) > 0 {
|
||||
// The relation key string of table filed name and attribute name
|
||||
// can be joined with char '=' or ':'.
|
||||
array := utils.SplitAndTrim(relationFields, "=")
|
||||
if len(array) == 1 {
|
||||
// Compatible with old splitting char ':'.
|
||||
array = utils.SplitAndTrim(relationFields, ":")
|
||||
}
|
||||
if len(array) == 1 {
|
||||
// The relation names are the same.
|
||||
array = []string{relationFields, relationFields}
|
||||
}
|
||||
if len(array) == 2 {
|
||||
// Defined table field to relation attribute name.
|
||||
// Like:
|
||||
// uid:Uid
|
||||
// uid:UserId
|
||||
relationFromFieldName = array[0]
|
||||
relationBindToFieldName = array[1]
|
||||
if key, _ := utils.MapPossibleItemByKey(maps[0], relationFromFieldName); key == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related table field name "%s" from given relation fields "%s"`,
|
||||
relationFromFieldName,
|
||||
relationFields,
|
||||
)
|
||||
} else {
|
||||
relationFromFieldName = key
|
||||
}
|
||||
} else {
|
||||
return gerror.NewCode(
|
||||
gcode.CodeInvalidParameter,
|
||||
`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`,
|
||||
)
|
||||
}
|
||||
if relationFromFieldName != "" {
|
||||
// Note that the value might be type of slice.
|
||||
relationDataMap = utils.ListToMapByKey(maps, relationFromFieldName)
|
||||
}
|
||||
if len(relationDataMap) == 0 {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find the relation data map, maybe invalid relation fields given "%v"`,
|
||||
relationFields,
|
||||
)
|
||||
}
|
||||
}
|
||||
// Bind to target attribute.
|
||||
var (
|
||||
ok bool
|
||||
bindToAttrValue reflect.Value
|
||||
bindToAttrKind reflect.Kind
|
||||
bindToAttrType reflect.Type
|
||||
bindToAttrField reflect.StructField
|
||||
)
|
||||
if arrayItemType.Kind() == reflect.Ptr {
|
||||
if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
}
|
||||
bindToAttrType = bindToAttrField.Type
|
||||
bindToAttrKind = bindToAttrType.Kind()
|
||||
|
||||
// Bind to relation conditions.
|
||||
var (
|
||||
relationFromAttrValue reflect.Value
|
||||
relationFromAttrField reflect.Value
|
||||
relationBindToFieldNameChecked bool
|
||||
)
|
||||
for i := 0; i < arrayValue.Len(); i++ {
|
||||
arrayElemValue := arrayValue.Index(i)
|
||||
// The FieldByName should be called on non-pointer reflect.Value.
|
||||
if arrayElemValue.Kind() == reflect.Ptr {
|
||||
// Like: []*Entity
|
||||
arrayElemValue = arrayElemValue.Elem()
|
||||
if !arrayElemValue.IsValid() {
|
||||
// The element is nil, then create one and set it to the slice.
|
||||
// The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
|
||||
// For example:
|
||||
// reflect.New(itemType.Elem()) => *Entity
|
||||
// reflect.New(itemType.Elem()).Elem() => Entity
|
||||
arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
|
||||
arrayValue.Index(i).Set(arrayElemValue.Addr())
|
||||
}
|
||||
} else {
|
||||
// Like: []Entity
|
||||
}
|
||||
bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName)
|
||||
if relationAttrName != "" {
|
||||
// Attribute value of current slice element.
|
||||
relationFromAttrValue = arrayElemValue.FieldByName(relationAttrName)
|
||||
if relationFromAttrValue.Kind() == reflect.Ptr {
|
||||
relationFromAttrValue = relationFromAttrValue.Elem()
|
||||
}
|
||||
} else {
|
||||
// Current slice element.
|
||||
relationFromAttrValue = arrayElemValue
|
||||
}
|
||||
if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
// Check and find possible bind to attribute name.
|
||||
if relationFields != "" && !relationBindToFieldNameChecked {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if !relationFromAttrField.IsValid() {
|
||||
var (
|
||||
filedMap, _ = structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: relationFromAttrValue,
|
||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
)
|
||||
if key, _ := utils.MapPossibleItemByKey(Map(filedMap), relationBindToFieldName); key == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related attribute name "%s" from given relation fields "%s"`,
|
||||
relationBindToFieldName,
|
||||
relationFields,
|
||||
)
|
||||
} else {
|
||||
relationBindToFieldName = key
|
||||
}
|
||||
}
|
||||
relationBindToFieldNameChecked = true
|
||||
}
|
||||
switch bindToAttrKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
//results := make(Result, 0)
|
||||
results := make([]interface{}, 0)
|
||||
for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) {
|
||||
item := v
|
||||
results = append(results, item)
|
||||
}
|
||||
if err = Structs(results, bindToAttrValue.Addr()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`relationKey should not be empty as field "%s" is slice`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
var element reflect.Value
|
||||
if bindToAttrValue.IsNil() {
|
||||
element = reflect.New(bindToAttrType.Elem()).Elem()
|
||||
} else {
|
||||
element = bindToAttrValue.Elem()
|
||||
}
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
v := relationDataMap[String(relationFromAttrField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if utils.IsSlice(v) {
|
||||
if err = Struct(SliceAny(v)[0], element); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(v, element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
if i >= len(maps) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
v := maps[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = Struct(v, element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bindToAttrValue.Set(element.Addr())
|
||||
|
||||
case reflect.Struct:
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
relationDataItem := relationDataMap[String(relationFromAttrField.Interface())]
|
||||
if relationDataItem == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if utils.IsSlice(relationDataItem) {
|
||||
if err = Struct(SliceAny(relationDataItem)[0], bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
if i >= len(maps) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
relationDataItem := maps[i]
|
||||
if relationDataItem == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
|
||||
}
|
||||
}
|
||||
reflect.ValueOf(structSlicePointer).Elem().Set(arrayValue)
|
||||
return nil
|
||||
}
|
||||
|
@ -319,3 +319,285 @@ func Test_Scan_SameType_Just_Assign(t *testing.T) {
|
||||
t.Assert(*m1["int"], *m2["int"])
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ScanList_Basic(t *testing.T) {
|
||||
// Struct attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int
|
||||
Address string
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int
|
||||
Uid int
|
||||
Score int
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
User EntityUser
|
||||
UserDetail EntityUserDetail
|
||||
UserScores []EntityUserScores
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
entities []Entity
|
||||
entityUsers = []EntityUser{
|
||||
{Uid: 1, Name: "name1"},
|
||||
{Uid: 2, Name: "name2"},
|
||||
{Uid: 3, Name: "name3"},
|
||||
}
|
||||
userDetails = []EntityUserDetail{
|
||||
{Uid: 1, Address: "address1"},
|
||||
{Uid: 2, Address: "address2"},
|
||||
}
|
||||
userScores = []EntityUserScores{
|
||||
{Id: 10, Uid: 1, Score: 100},
|
||||
{Id: 11, Uid: 1, Score: 60},
|
||||
{Id: 20, Uid: 2, Score: 99},
|
||||
}
|
||||
)
|
||||
err = gconv.ScanList(entityUsers, &entities, "User")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userDetails, &entities, "UserDetail", "User", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userScores, &entities, "UserScores", "User", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(entities), 3)
|
||||
t.Assert(entities[0].User, entityUsers[0])
|
||||
t.Assert(entities[1].User, entityUsers[1])
|
||||
t.Assert(entities[2].User, entityUsers[2])
|
||||
|
||||
t.Assert(entities[0].UserDetail, userDetails[0])
|
||||
t.Assert(entities[1].UserDetail, userDetails[1])
|
||||
t.Assert(entities[2].UserDetail, EntityUserDetail{})
|
||||
|
||||
t.Assert(len(entities[0].UserScores), 2)
|
||||
t.Assert(entities[0].UserScores[0], userScores[0])
|
||||
t.Assert(entities[0].UserScores[1], userScores[1])
|
||||
|
||||
t.Assert(len(entities[1].UserScores), 1)
|
||||
t.Assert(entities[1].UserScores[0], userScores[2])
|
||||
|
||||
t.Assert(len(entities[2].UserScores), 0)
|
||||
})
|
||||
// Pointer attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int
|
||||
Address string
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int
|
||||
Uid int
|
||||
Score int
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
User *EntityUser
|
||||
UserDetail *EntityUserDetail
|
||||
UserScores []*EntityUserScores
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
entities []*Entity
|
||||
entityUsers = []*EntityUser{
|
||||
{Uid: 1, Name: "name1"},
|
||||
{Uid: 2, Name: "name2"},
|
||||
{Uid: 3, Name: "name3"},
|
||||
}
|
||||
userDetails = []*EntityUserDetail{
|
||||
{Uid: 1, Address: "address1"},
|
||||
{Uid: 2, Address: "address2"},
|
||||
}
|
||||
userScores = []*EntityUserScores{
|
||||
{Id: 10, Uid: 1, Score: 100},
|
||||
{Id: 11, Uid: 1, Score: 60},
|
||||
{Id: 20, Uid: 2, Score: 99},
|
||||
}
|
||||
)
|
||||
err = gconv.ScanList(entityUsers, &entities, "User")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userDetails, &entities, "UserDetail", "User", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userScores, &entities, "UserScores", "User", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(entities), 3)
|
||||
t.Assert(entities[0].User, entityUsers[0])
|
||||
t.Assert(entities[1].User, entityUsers[1])
|
||||
t.Assert(entities[2].User, entityUsers[2])
|
||||
|
||||
t.Assert(entities[0].UserDetail, userDetails[0])
|
||||
t.Assert(entities[1].UserDetail, userDetails[1])
|
||||
t.Assert(entities[2].UserDetail, nil)
|
||||
|
||||
t.Assert(len(entities[0].UserScores), 2)
|
||||
t.Assert(entities[0].UserScores[0], userScores[0])
|
||||
t.Assert(entities[0].UserScores[1], userScores[1])
|
||||
|
||||
t.Assert(len(entities[1].UserScores), 1)
|
||||
t.Assert(entities[1].UserScores[0], userScores[2])
|
||||
|
||||
t.Assert(len(entities[2].UserScores), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ScanList_Embedded(t *testing.T) {
|
||||
// Struct attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int
|
||||
Address string
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int
|
||||
Uid int
|
||||
Score int
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
EntityUser
|
||||
UserDetail EntityUserDetail
|
||||
UserScores []EntityUserScores
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
entities []Entity
|
||||
entityUsers = []EntityUser{
|
||||
{Uid: 1, Name: "name1"},
|
||||
{Uid: 2, Name: "name2"},
|
||||
{Uid: 3, Name: "name3"},
|
||||
}
|
||||
userDetails = []EntityUserDetail{
|
||||
{Uid: 1, Address: "address1"},
|
||||
{Uid: 2, Address: "address2"},
|
||||
}
|
||||
userScores = []EntityUserScores{
|
||||
{Id: 10, Uid: 1, Score: 100},
|
||||
{Id: 11, Uid: 1, Score: 60},
|
||||
{Id: 20, Uid: 2, Score: 99},
|
||||
}
|
||||
)
|
||||
err = gconv.Scan(entityUsers, &entities)
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userDetails, &entities, "UserDetail", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userScores, &entities, "UserScores", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(entities), 3)
|
||||
t.Assert(entities[0].EntityUser, entityUsers[0])
|
||||
t.Assert(entities[1].EntityUser, entityUsers[1])
|
||||
t.Assert(entities[2].EntityUser, entityUsers[2])
|
||||
|
||||
t.Assert(entities[0].UserDetail, userDetails[0])
|
||||
t.Assert(entities[1].UserDetail, userDetails[1])
|
||||
t.Assert(entities[2].UserDetail, EntityUserDetail{})
|
||||
|
||||
t.Assert(len(entities[0].UserScores), 2)
|
||||
t.Assert(entities[0].UserScores[0], userScores[0])
|
||||
t.Assert(entities[0].UserScores[1], userScores[1])
|
||||
|
||||
t.Assert(len(entities[1].UserScores), 1)
|
||||
t.Assert(entities[1].UserScores[0], userScores[2])
|
||||
|
||||
t.Assert(len(entities[2].UserScores), 0)
|
||||
})
|
||||
// Pointer attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int
|
||||
Address string
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int
|
||||
Uid int
|
||||
Score int
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
*EntityUser
|
||||
UserDetail *EntityUserDetail
|
||||
UserScores []*EntityUserScores
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
entities []Entity
|
||||
entityUsers = []EntityUser{
|
||||
{Uid: 1, Name: "name1"},
|
||||
{Uid: 2, Name: "name2"},
|
||||
{Uid: 3, Name: "name3"},
|
||||
}
|
||||
userDetails = []EntityUserDetail{
|
||||
{Uid: 1, Address: "address1"},
|
||||
{Uid: 2, Address: "address2"},
|
||||
}
|
||||
userScores = []EntityUserScores{
|
||||
{Id: 10, Uid: 1, Score: 100},
|
||||
{Id: 11, Uid: 1, Score: 60},
|
||||
{Id: 20, Uid: 2, Score: 99},
|
||||
}
|
||||
)
|
||||
err = gconv.Scan(entityUsers, &entities)
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userDetails, &entities, "UserDetail", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userScores, &entities, "UserScores", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(entities), 3)
|
||||
t.Assert(entities[0].EntityUser, entityUsers[0])
|
||||
t.Assert(entities[1].EntityUser, entityUsers[1])
|
||||
t.Assert(entities[2].EntityUser, entityUsers[2])
|
||||
|
||||
t.Assert(entities[0].UserDetail, userDetails[0])
|
||||
t.Assert(entities[1].UserDetail, userDetails[1])
|
||||
t.Assert(entities[2].UserDetail, nil)
|
||||
|
||||
t.Assert(len(entities[0].UserScores), 2)
|
||||
t.Assert(entities[0].UserScores[0], userScores[0])
|
||||
t.Assert(entities[0].UserScores[1], userScores[1])
|
||||
|
||||
t.Assert(len(entities[1].UserScores), 1)
|
||||
t.Assert(entities[1].UserScores[0], userScores[2])
|
||||
|
||||
t.Assert(len(entities[2].UserScores), 0)
|
||||
})
|
||||
}
|
||||
|
@ -15,6 +15,16 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// iString is used for type assert api for String().
|
||||
type iString interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// iMarshalJSON is the interface for custom Json marshaling.
|
||||
type iMarshalJSON interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
// ExportOption specifies the behavior of function Export.
|
||||
type ExportOption struct {
|
||||
WithoutType bool // WithoutType specifies exported content has no type information.
|
||||
@ -151,7 +161,7 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
|
||||
"%s%v:%s",
|
||||
newIndent,
|
||||
mapKeyStr,
|
||||
gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(
|
||||
@ -159,7 +169,7 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
|
||||
newIndent,
|
||||
mapKey.Type().String(),
|
||||
mapKeyStr,
|
||||
gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
))
|
||||
}
|
||||
doExport(reflectValue.MapIndex(mapKey).Interface(), newIndent, buffer, option)
|
||||
@ -173,10 +183,35 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
|
||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
if len(structFields) == 0 {
|
||||
if option.WithoutType {
|
||||
buffer.WriteString("{}")
|
||||
var (
|
||||
structContentStr = ""
|
||||
attributeCountStr = "0"
|
||||
)
|
||||
if v, ok := value.(iString); ok {
|
||||
structContentStr = v.String()
|
||||
} else if v, ok := value.(iMarshalJSON); ok {
|
||||
b, _ := v.MarshalJSON()
|
||||
structContentStr = string(b)
|
||||
}
|
||||
if structContentStr == "" {
|
||||
structContentStr = "{}"
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("%s(0) {}", reflectTypeName))
|
||||
if strings.HasPrefix(structContentStr, `"`) && strings.HasSuffix(structContentStr, `"`) {
|
||||
attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr))
|
||||
} else {
|
||||
structContentStr = fmt.Sprintf(`"%s"`, gstr.AddSlashes(structContentStr))
|
||||
attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)-2)
|
||||
}
|
||||
}
|
||||
if option.WithoutType {
|
||||
buffer.WriteString(structContentStr)
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(
|
||||
"%s(%s) %s",
|
||||
reflectTypeName,
|
||||
attributeCountStr,
|
||||
structContentStr,
|
||||
))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -202,7 +237,7 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
|
||||
"%s%s:%s",
|
||||
newIndent,
|
||||
field.Name(),
|
||||
gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
))
|
||||
doExport(field.Value.Interface(), newIndent, buffer, option)
|
||||
buffer.WriteString(",\n")
|
||||
|
@ -7,6 +7,7 @@
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -130,3 +131,9 @@ func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) [
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// ListToMapByKey converts `list` to a map[string]interface{} of which key is specified by `key`.
|
||||
// Note that the item value may be type of slice.
|
||||
func ListToMapByKey(list []map[string]interface{}, key string) map[string]interface{} {
|
||||
return utils.ListToMapByKey(list, key)
|
||||
}
|
||||
|
@ -67,19 +67,7 @@ func MapMergeCopy(src ...map[string]interface{}) (copy map[string]interface{}) {
|
||||
//
|
||||
// Note that this function might be of low performance.
|
||||
func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey string, foundValue interface{}) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
if v, ok := data[key]; ok {
|
||||
return key, v
|
||||
}
|
||||
// Loop checking.
|
||||
for k, v := range data {
|
||||
if utils.EqualFoldWithoutChars(k, key) {
|
||||
return k, v
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
return utils.MapPossibleItemByKey(data, key)
|
||||
}
|
||||
|
||||
// MapContainsPossibleKey checks if the given `key` is contained in given map `data`.
|
||||
|
128
util/gutil/gutil_z_unit_dump_test.go
Executable file
128
util/gutil/gutil_z_unit_dump_test.go
Executable file
@ -0,0 +1,128 @@
|
||||
// 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 gutil_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
func Test_Dump(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
|
||||
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
|
||||
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
|
||||
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
|
||||
Name string
|
||||
CreatedAt *gtime.Time
|
||||
SetMap map[string]*SetSpecInfo
|
||||
SetSlice []SetSpecInfo
|
||||
Handler ghttp.HandlerFunc
|
||||
internal string
|
||||
}
|
||||
req := &CreateResourceReq{
|
||||
CommonReq: CommonReq{
|
||||
AppId: 12345678,
|
||||
ResourceId: "tdchqy-xxx",
|
||||
},
|
||||
Name: "john",
|
||||
CreatedAt: gtime.Now(),
|
||||
SetMap: map[string]*SetSpecInfo{
|
||||
"test1": {
|
||||
StorageType: "ssd",
|
||||
Shards: 2,
|
||||
Params: []string{"a", "b", "c"},
|
||||
},
|
||||
"test2": {
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{},
|
||||
},
|
||||
},
|
||||
SetSlice: []SetSpecInfo{
|
||||
{
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{"h"},
|
||||
},
|
||||
},
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gutil.Dump(map[int]int{
|
||||
100: 100,
|
||||
})
|
||||
gutil.Dump(req)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDumpWithType(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
|
||||
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
|
||||
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
|
||||
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
|
||||
Name string
|
||||
CreatedAt *gtime.Time
|
||||
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
|
||||
Handler ghttp.HandlerFunc
|
||||
internal string
|
||||
}
|
||||
req := &CreateResourceReq{
|
||||
CommonReq: CommonReq{
|
||||
AppId: 12345678,
|
||||
ResourceId: "tdchqy-xxx",
|
||||
},
|
||||
Name: "john",
|
||||
CreatedAt: gtime.Now(),
|
||||
SetMap: map[string]*SetSpecInfo{
|
||||
"test1": {
|
||||
StorageType: "ssd",
|
||||
Shards: 2,
|
||||
Params: []string{"a", "b", "c"},
|
||||
},
|
||||
"test2": {
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{},
|
||||
},
|
||||
},
|
||||
SetSlice: []SetSpecInfo{
|
||||
{
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{"h"},
|
||||
},
|
||||
},
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gutil.DumpWithType(map[int]int{
|
||||
100: 100,
|
||||
})
|
||||
gutil.DumpWithType(req)
|
||||
gutil.DumpWithType([][]byte{[]byte("hello")})
|
||||
})
|
||||
}
|
@ -8,129 +8,12 @@ package gutil_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
func Test_Dump(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
|
||||
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
|
||||
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
|
||||
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
|
||||
Name string `des:"实例名称"`
|
||||
Product string `des:"业务类型"`
|
||||
Region string `v:"required" des:"区域"`
|
||||
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
|
||||
Handler ghttp.HandlerFunc
|
||||
internal string
|
||||
}
|
||||
req := &CreateResourceReq{
|
||||
CommonReq: CommonReq{
|
||||
AppId: 12345678,
|
||||
ResourceId: "tdchqy-xxx",
|
||||
},
|
||||
Name: "john",
|
||||
Product: "goframe",
|
||||
Region: "cd",
|
||||
SetMap: map[string]*SetSpecInfo{
|
||||
"test1": {
|
||||
StorageType: "ssd",
|
||||
Shards: 2,
|
||||
Params: []string{"a", "b", "c"},
|
||||
},
|
||||
"test2": {
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{},
|
||||
},
|
||||
},
|
||||
SetSlice: []SetSpecInfo{
|
||||
{
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{"h"},
|
||||
},
|
||||
},
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gutil.Dump(map[int]int{
|
||||
100: 100,
|
||||
})
|
||||
gutil.Dump(req)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDumpWithType(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
|
||||
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
|
||||
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
|
||||
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
|
||||
Name string `des:"实例名称"`
|
||||
Product string `des:"业务类型"`
|
||||
Region string `v:"required" des:"区域"`
|
||||
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
|
||||
Handler ghttp.HandlerFunc
|
||||
internal string
|
||||
}
|
||||
req := &CreateResourceReq{
|
||||
CommonReq: CommonReq{
|
||||
AppId: 12345678,
|
||||
ResourceId: "tdchqy-xxx",
|
||||
},
|
||||
Name: "john",
|
||||
Product: "goframe",
|
||||
Region: "cd",
|
||||
SetMap: map[string]*SetSpecInfo{
|
||||
"test1": {
|
||||
StorageType: "ssd",
|
||||
Shards: 2,
|
||||
Params: []string{"a", "b", "c"},
|
||||
},
|
||||
"test2": {
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{},
|
||||
},
|
||||
},
|
||||
SetSlice: []SetSpecInfo{
|
||||
{
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{"h"},
|
||||
},
|
||||
},
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gutil.DumpWithType(map[int]int{
|
||||
100: 100,
|
||||
})
|
||||
gutil.DumpWithType(req)
|
||||
gutil.DumpWithType([][]byte{[]byte("hello")})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Try(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := `gutil Try test`
|
||||
|
@ -6,16 +6,29 @@
|
||||
|
||||
package gvalid
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
)
|
||||
|
||||
// RuleFunc is the custom function for data validation.
|
||||
//
|
||||
// The parameter `rule` specifies the validation rule string, like "required", "between:1,100", etc.
|
||||
// The parameter `value` specifies the value for this rule to validate.
|
||||
// The parameter `message` specifies the custom error message or configured i18n message for this rule.
|
||||
// The parameter `data` specifies the `data` which is passed to the Validator. It might be a type of map/struct or a nil value.
|
||||
// You can ignore the parameter `data` if you do not really need it in your custom validation rule.
|
||||
type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error
|
||||
type RuleFunc func(ctx context.Context, in RuleFuncInput) error
|
||||
|
||||
// RuleFuncInput holds the input parameters that passed to custom rule function RuleFunc.
|
||||
type RuleFuncInput struct {
|
||||
// Rule specifies the validation rule string, like "required", "between:1,100", etc.
|
||||
Rule string
|
||||
|
||||
// Message specifies the custom error message or configured i18n message for this rule.
|
||||
Message string
|
||||
|
||||
// Value specifies the value for this rule to validate.
|
||||
Value *gvar.Var
|
||||
|
||||
// Data specifies the `data` which is passed to the Validator. It might be a type of map/struct or a nil value.
|
||||
// You can ignore the parameter `Data` if you do not really need it in your custom validation rule.
|
||||
Data *gvar.Var
|
||||
}
|
||||
|
||||
var (
|
||||
// customRuleFuncMap stores the custom rule functions.
|
||||
@ -24,13 +37,20 @@ var (
|
||||
)
|
||||
|
||||
// RegisterRule registers custom validation rule and function for package.
|
||||
// It returns error if there's already the same rule registered previously.
|
||||
func RegisterRule(rule string, f RuleFunc) error {
|
||||
func RegisterRule(rule string, f RuleFunc) {
|
||||
customRuleFuncMap[rule] = f
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRule deletes custom defined validation rule and its function from global package.
|
||||
func DeleteRule(rule string) {
|
||||
delete(customRuleFuncMap, rule)
|
||||
// RegisterRuleByMap registers custom validation rules using map for package.
|
||||
func RegisterRuleByMap(m map[string]RuleFunc) {
|
||||
for k, v := range m {
|
||||
customRuleFuncMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteRule deletes custom defined validation one or more rules and associated functions from global package.
|
||||
func DeleteRule(rules ...string) {
|
||||
for _, rule := range rules {
|
||||
delete(customRuleFuncMap, rule)
|
||||
}
|
||||
}
|
||||
|
@ -19,50 +19,62 @@ type Error interface {
|
||||
Code() gcode.Code
|
||||
Current() error
|
||||
Error() string
|
||||
FirstItem() (key string, messages map[string]string)
|
||||
FirstRule() (rule string, err string)
|
||||
FirstString() (err string)
|
||||
Items() (items []map[string]map[string]string)
|
||||
Map() map[string]string
|
||||
Maps() map[string]map[string]string
|
||||
FirstItem() (key string, messages map[string]error)
|
||||
FirstRule() (rule string, err error)
|
||||
FirstError() (err error)
|
||||
Items() (items []map[string]map[string]error)
|
||||
Map() map[string]error
|
||||
Maps() map[string]map[string]error
|
||||
String() string
|
||||
Strings() (errs []string)
|
||||
}
|
||||
|
||||
// validationError is the validation error for validation result.
|
||||
type validationError struct {
|
||||
code gcode.Code // Error code.
|
||||
rules []fieldRule // Rules by sequence, which is used for keeping error sequence.
|
||||
errors map[string]map[string]string // Error map:map[field]map[rule]message
|
||||
firstKey string // The first error rule key(empty in default).
|
||||
firstItem map[string]string // The first error rule value(nil in default).
|
||||
code gcode.Code // Error code.
|
||||
rules []fieldRule // Rules by sequence, which is used for keeping error sequence.
|
||||
errors map[string]map[string]error // Error map:map[field]map[rule]message
|
||||
firstKey string // The first error rule key(empty in default).
|
||||
firstItem map[string]error // The first error rule value(nil in default).
|
||||
}
|
||||
|
||||
// newError creates and returns a validation error.
|
||||
func newError(code gcode.Code, rules []fieldRule, errors map[string]map[string]string) *validationError {
|
||||
for field, m := range errors {
|
||||
for k, v := range m {
|
||||
v = strings.Replace(v, ":attribute", field, -1)
|
||||
v, _ = gregex.ReplaceString(`\s{2,}`, ` `, v)
|
||||
v = gstr.Trim(v)
|
||||
m[k] = v
|
||||
// newValidationError creates and returns a validation error.
|
||||
func newValidationError(code gcode.Code, rules []fieldRule, fieldRuleErrorMap map[string]map[string]error) *validationError {
|
||||
var (
|
||||
s string
|
||||
)
|
||||
for field, ruleErrorMap := range fieldRuleErrorMap {
|
||||
for rule, err := range ruleErrorMap {
|
||||
if !gerror.HasStack(err) {
|
||||
s = strings.Replace(err.Error(), ":attribute", field, -1)
|
||||
s, _ = gregex.ReplaceString(`\s{2,}`, ` `, s)
|
||||
ruleErrorMap[rule] = gerror.NewOption(gerror.Option{
|
||||
Stack: false,
|
||||
Text: gstr.Trim(s),
|
||||
Code: code,
|
||||
})
|
||||
}
|
||||
}
|
||||
errors[field] = m
|
||||
fieldRuleErrorMap[field] = ruleErrorMap
|
||||
}
|
||||
return &validationError{
|
||||
code: code,
|
||||
rules: rules,
|
||||
errors: errors,
|
||||
errors: fieldRuleErrorMap,
|
||||
}
|
||||
}
|
||||
|
||||
// newErrorStr creates and returns a validation error by string.
|
||||
func newErrorStr(key, err string) *validationError {
|
||||
return newError(gcode.CodeInternalError, nil, map[string]map[string]string{
|
||||
internalErrorMapKey: {
|
||||
key: err,
|
||||
// newValidationErrorByStr creates and returns a validation error by string.
|
||||
func newValidationErrorByStr(key string, err error) *validationError {
|
||||
return newValidationError(
|
||||
gcode.CodeInternalError,
|
||||
nil,
|
||||
map[string]map[string]error{
|
||||
internalErrorMapKey: {
|
||||
key: err,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Code returns the error code of current validation error.
|
||||
@ -74,16 +86,16 @@ func (e *validationError) Code() gcode.Code {
|
||||
}
|
||||
|
||||
// Map returns the first error message as map.
|
||||
func (e *validationError) Map() map[string]string {
|
||||
func (e *validationError) Map() map[string]error {
|
||||
if e == nil {
|
||||
return map[string]string{}
|
||||
return map[string]error{}
|
||||
}
|
||||
_, m := e.FirstItem()
|
||||
return m
|
||||
}
|
||||
|
||||
// Maps returns all error messages as map.
|
||||
func (e *validationError) Maps() map[string]map[string]string {
|
||||
func (e *validationError) Maps() map[string]map[string]error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
@ -92,16 +104,16 @@ func (e *validationError) Maps() map[string]map[string]string {
|
||||
|
||||
// Items retrieves and returns error items array in sequence if possible,
|
||||
// or else it returns error items with no sequence .
|
||||
func (e *validationError) Items() (items []map[string]map[string]string) {
|
||||
func (e *validationError) Items() (items []map[string]map[string]error) {
|
||||
if e == nil {
|
||||
return []map[string]map[string]string{}
|
||||
return []map[string]map[string]error{}
|
||||
}
|
||||
items = make([]map[string]map[string]string, 0)
|
||||
items = make([]map[string]map[string]error, 0)
|
||||
// By sequence.
|
||||
if len(e.rules) > 0 {
|
||||
for _, v := range e.rules {
|
||||
if errorItemMap, ok := e.errors[v.Name]; ok {
|
||||
items = append(items, map[string]map[string]string{
|
||||
items = append(items, map[string]map[string]error{
|
||||
v.Name: errorItemMap,
|
||||
})
|
||||
}
|
||||
@ -110,7 +122,7 @@ func (e *validationError) Items() (items []map[string]map[string]string) {
|
||||
}
|
||||
// No sequence.
|
||||
for name, errorRuleMap := range e.errors {
|
||||
items = append(items, map[string]map[string]string{
|
||||
items = append(items, map[string]map[string]error{
|
||||
name: errorRuleMap,
|
||||
})
|
||||
}
|
||||
@ -118,9 +130,9 @@ func (e *validationError) Items() (items []map[string]map[string]string) {
|
||||
}
|
||||
|
||||
// FirstItem returns the field name and error messages for the first validation rule error.
|
||||
func (e *validationError) FirstItem() (key string, messages map[string]string) {
|
||||
func (e *validationError) FirstItem() (key string, messages map[string]error) {
|
||||
if e == nil {
|
||||
return "", map[string]string{}
|
||||
return "", map[string]error{}
|
||||
}
|
||||
if e.firstItem != nil {
|
||||
return e.firstKey, e.firstItem
|
||||
@ -145,9 +157,9 @@ func (e *validationError) FirstItem() (key string, messages map[string]string) {
|
||||
}
|
||||
|
||||
// FirstRule returns the first error rule and message string.
|
||||
func (e *validationError) FirstRule() (rule string, err string) {
|
||||
func (e *validationError) FirstRule() (rule string, err error) {
|
||||
if e == nil {
|
||||
return "", ""
|
||||
return "", nil
|
||||
}
|
||||
// By sequence.
|
||||
if len(e.rules) > 0 {
|
||||
@ -169,26 +181,22 @@ func (e *validationError) FirstRule() (rule string, err string) {
|
||||
return k, v
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// FirstString returns the first error message as string.
|
||||
// FirstError returns the first error message as string.
|
||||
// Note that the returned message might be different if it has no sequence.
|
||||
func (e *validationError) FirstString() (err string) {
|
||||
func (e *validationError) FirstError() (err error) {
|
||||
if e == nil {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
_, err = e.FirstRule()
|
||||
return
|
||||
}
|
||||
|
||||
// Current is alis of FirstString, which implements interface gerror.iCurrent.
|
||||
// Current is alis of FirstError, which implements interface gerror.iCurrent.
|
||||
func (e *validationError) Current() error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := e.FirstRule()
|
||||
return gerror.NewCode(e.code, err)
|
||||
return e.FirstError()
|
||||
}
|
||||
|
||||
// String returns all error messages as string, multiple error messages joined using char ';'.
|
||||
@ -221,13 +229,13 @@ func (e *validationError) Strings() (errs []string) {
|
||||
for _, ruleItem := range strings.Split(v.Rule, "|") {
|
||||
ruleItem = strings.TrimSpace(strings.Split(ruleItem, ":")[0])
|
||||
if err, ok := errorItemMap[ruleItem]; ok {
|
||||
errs = append(errs, err)
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
// internal error checks.
|
||||
for k, _ := range internalErrKeyMap {
|
||||
if err, ok := errorItemMap[k]; ok {
|
||||
errs = append(errs, err)
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -237,7 +245,7 @@ func (e *validationError) Strings() (errs []string) {
|
||||
// No sequence.
|
||||
for _, errorItemMap := range e.errors {
|
||||
for _, err := range errorItemMap {
|
||||
errs = append(errs, err)
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
return
|
||||
|
@ -8,6 +8,7 @@ package gvalid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"strings"
|
||||
@ -27,7 +28,7 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
|
||||
var (
|
||||
checkRules = make([]fieldRule, 0)
|
||||
customMessage = make(CustomMsg) // map[RuleKey]ErrorMsg.
|
||||
errorMaps = make(map[string]map[string]string)
|
||||
errorMaps = make(map[string]map[string]error)
|
||||
)
|
||||
switch assertValue := v.rules.(type) {
|
||||
// Sequence tag: []sequence tag
|
||||
@ -80,9 +81,9 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
|
||||
}
|
||||
data := gconv.Map(params)
|
||||
if data == nil {
|
||||
return newErrorStr(
|
||||
return newValidationErrorByStr(
|
||||
internalParamsErrRuleName,
|
||||
"invalid params type: convert to map failed",
|
||||
errors.New("invalid params type: convert to map failed"),
|
||||
)
|
||||
}
|
||||
if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 {
|
||||
@ -139,7 +140,7 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
|
||||
}
|
||||
}
|
||||
if _, ok := errorMaps[checkRuleItem.Name]; !ok {
|
||||
errorMaps[checkRuleItem.Name] = make(map[string]string)
|
||||
errorMaps[checkRuleItem.Name] = make(map[string]error)
|
||||
}
|
||||
for ruleKey, errorItemMsgMap := range errorItem {
|
||||
errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap
|
||||
@ -150,7 +151,7 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
|
||||
}
|
||||
}
|
||||
if len(errorMaps) > 0 {
|
||||
return newError(gcode.CodeValidationFailed, checkRules, errorMaps)
|
||||
return newValidationError(gcode.CodeValidationFailed, checkRules, errorMaps)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ func (v *Validator) CheckStruct(ctx context.Context, object interface{}) Error {
|
||||
|
||||
func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error {
|
||||
var (
|
||||
errorMaps = make(map[string]map[string]string) // Returning error.
|
||||
fieldToAliasNameMap = make(map[string]string) // Field names to alias name map.
|
||||
errorMaps = make(map[string]map[string]error) // Returning error.
|
||||
fieldToAliasNameMap = make(map[string]string) // Field names to alias name map.
|
||||
)
|
||||
fieldMap, err := structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: object,
|
||||
@ -32,7 +32,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
RecursiveOption: structs.RecursiveOptionEmbedded,
|
||||
})
|
||||
if err != nil {
|
||||
return newErrorStr(internalObjectErrRuleName, err.Error())
|
||||
return newValidationErrorByStr(internalObjectErrRuleName, err)
|
||||
}
|
||||
// It checks the struct recursively if its attribute is an embedded struct.
|
||||
for _, field := range fieldMap {
|
||||
@ -59,7 +59,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
// It here must use structs.TagFields not structs.FieldMap to ensure error sequence.
|
||||
tagField, err := structs.TagFields(object, structTagPriority)
|
||||
if err != nil {
|
||||
return newErrorStr(internalObjectErrRuleName, err.Error())
|
||||
return newValidationErrorByStr(internalObjectErrRuleName, err)
|
||||
}
|
||||
// If there's no struct tag and validation rules, it does nothing and returns quickly.
|
||||
if len(tagField) == 0 && v.messages == nil {
|
||||
@ -278,7 +278,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
}
|
||||
}
|
||||
if _, ok := errorMaps[checkRuleItem.Name]; !ok {
|
||||
errorMaps[checkRuleItem.Name] = make(map[string]string)
|
||||
errorMaps[checkRuleItem.Name] = make(map[string]error)
|
||||
}
|
||||
for ruleKey, errorItemMsgMap := range errorItem {
|
||||
errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap
|
||||
@ -289,7 +289,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
}
|
||||
}
|
||||
if len(errorMaps) > 0 {
|
||||
return newError(gcode.CodeValidationFailed, checkRules, errorMaps)
|
||||
return newValidationError(gcode.CodeValidationFailed, checkRules, errorMaps)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -8,8 +8,9 @@ package gvalid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -60,7 +61,7 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
// It converts value to string and then does the validation.
|
||||
var (
|
||||
// Do not trim it as the space is also part of the value.
|
||||
errorMsgArray = make(map[string]string)
|
||||
ruleErrorMap = make(map[string]error)
|
||||
)
|
||||
// Custom error messages handling.
|
||||
var (
|
||||
@ -86,9 +87,9 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
ruleItems[i-1] += "|" + ruleItems[i]
|
||||
ruleItems = append(ruleItems[:i], ruleItems[i+1:]...)
|
||||
} else {
|
||||
return newErrorStr(
|
||||
return newValidationErrorByStr(
|
||||
internalRulesErrRuleName,
|
||||
internalRulesErrRuleName+": "+input.Rule,
|
||||
errors.New(internalRulesErrRuleName+": "+input.Rule),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -133,9 +134,14 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
if customRuleFunc != nil {
|
||||
// It checks custom validation rules with most priority.
|
||||
message := v.getErrorMessageByRule(ctx, ruleKey, customMsgMap)
|
||||
if err := customRuleFunc(ctx, ruleItems[index], input.Value, message, input.DataRaw); err != nil {
|
||||
if err := customRuleFunc(ctx, RuleFuncInput{
|
||||
Rule: ruleItems[index],
|
||||
Message: message,
|
||||
Value: gvar.New(input.Value),
|
||||
Data: gvar.New(input.DataRaw),
|
||||
}); err != nil {
|
||||
match = false
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
ruleErrorMap[ruleKey] = err
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
@ -154,7 +160,7 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
},
|
||||
)
|
||||
if !match && err != nil {
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
ruleErrorMap[ruleKey] = err
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,8 +168,8 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
if !match {
|
||||
// It does nothing if the error message for this rule
|
||||
// is already set in previous validation.
|
||||
if _, ok := errorMsgArray[ruleKey]; !ok {
|
||||
errorMsgArray[ruleKey] = v.getErrorMessageByRule(ctx, ruleKey, customMsgMap)
|
||||
if _, ok := ruleErrorMap[ruleKey]; !ok {
|
||||
ruleErrorMap[ruleKey] = errors.New(v.getErrorMessageByRule(ctx, ruleKey, customMsgMap))
|
||||
}
|
||||
// If it is with error and there's bail rule,
|
||||
// it then does not continue validating for left rules.
|
||||
@ -173,12 +179,12 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
}
|
||||
index++
|
||||
}
|
||||
if len(errorMsgArray) > 0 {
|
||||
return newError(
|
||||
if len(ruleErrorMap) > 0 {
|
||||
return newValidationError(
|
||||
gcode.CodeValidationFailed,
|
||||
[]fieldRule{{Name: input.Name, Rule: input.Rule}},
|
||||
map[string]map[string]string{
|
||||
input.Name: errorMsgArray,
|
||||
map[string]map[string]error{
|
||||
input.Name: ruleErrorMap,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -217,10 +223,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI
|
||||
"max-length",
|
||||
"size":
|
||||
if msg := v.checkLength(ctx, valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" {
|
||||
return match, gerror.NewOption(gerror.Option{
|
||||
Text: msg,
|
||||
Code: gcode.CodeValidationFailed,
|
||||
})
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
@ -231,10 +234,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI
|
||||
"max",
|
||||
"between":
|
||||
if msg := v.checkRange(ctx, valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" {
|
||||
return match, gerror.NewOption(gerror.Option{
|
||||
Text: msg,
|
||||
Code: gcode.CodeValidationFailed,
|
||||
})
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
@ -282,10 +282,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI
|
||||
)
|
||||
msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap)
|
||||
msg = strings.Replace(msg, ":format", input.RulePattern, -1)
|
||||
return match, gerror.NewOption(gerror.Option{
|
||||
Text: msg,
|
||||
Code: gcode.CodeValidationFailed,
|
||||
})
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should be equal as string.
|
||||
@ -300,10 +297,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI
|
||||
var msg string
|
||||
msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap)
|
||||
msg = strings.Replace(msg, ":field", input.RulePattern, -1)
|
||||
return match, gerror.NewOption(gerror.Option{
|
||||
Text: msg,
|
||||
Code: gcode.CodeValidationFailed,
|
||||
})
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should not be equal as string.
|
||||
@ -319,10 +313,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI
|
||||
var msg string
|
||||
msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap)
|
||||
msg = strings.Replace(msg, ":field", input.RulePattern, -1)
|
||||
return match, gerror.NewOption(gerror.Option{
|
||||
Text: msg,
|
||||
Code: gcode.CodeValidationFailed,
|
||||
})
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Field value should be in range of.
|
||||
@ -505,10 +496,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, valueStr)
|
||||
|
||||
default:
|
||||
return match, gerror.NewOption(gerror.Option{
|
||||
Text: "Invalid rule name: " + input.RuleKey,
|
||||
Code: gcode.CodeInvalidParameter,
|
||||
})
|
||||
return match, errors.New("Invalid rule name: " + input.RuleKey)
|
||||
}
|
||||
return match, nil
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ func ExampleCheckMap() {
|
||||
if e := gvalid.CheckMap(gctx.New(), params, rules); e != nil {
|
||||
fmt.Println(e.Map())
|
||||
fmt.Println(e.FirstItem())
|
||||
fmt.Println(e.FirstString())
|
||||
fmt.Println(e.FirstError())
|
||||
}
|
||||
// May Output:
|
||||
// map[required:账号不能为空 length:账号长度应当在6到16之间]
|
||||
@ -55,7 +55,7 @@ func ExampleCheckMap2() {
|
||||
if e := gvalid.CheckMap(gctx.New(), params, rules); e != nil {
|
||||
fmt.Println(e.Map())
|
||||
fmt.Println(e.FirstItem())
|
||||
fmt.Println(e.FirstString())
|
||||
fmt.Println(e.FirstError())
|
||||
}
|
||||
// Output:
|
||||
// map[same:两次密码输入不相等]
|
||||
@ -127,17 +127,17 @@ func ExampleRegisterRule() {
|
||||
}
|
||||
|
||||
rule := "unique-name"
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
|
||||
var (
|
||||
id = data.(*User).Id
|
||||
name = gconv.String(value)
|
||||
id = in.Data.Val().(*User).Id
|
||||
name = gconv.String(in.Value)
|
||||
)
|
||||
n, err := g.Model("user").Where("id != ? and name = ?", id, name).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
return errors.New(message)
|
||||
return errors.New(in.Message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -149,8 +149,8 @@ func ExampleRegisterRule() {
|
||||
|
||||
func ExampleRegisterRule_OverwriteRequired() {
|
||||
rule := "required"
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
reflectValue := reflect.ValueOf(value)
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
|
||||
reflectValue := reflect.ValueOf(in.Value.Val())
|
||||
if reflectValue.Kind() == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
@ -171,7 +171,7 @@ func ExampleRegisterRule_OverwriteRequired() {
|
||||
isEmpty = reflectValue.Len() == 0
|
||||
}
|
||||
if isEmpty {
|
||||
return errors.New(message)
|
||||
return errors.New(in.Message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -1037,7 +1037,7 @@ func Test_InternalError_String(t *testing.T) {
|
||||
|
||||
t.Assert(err.String(), "InvalidRules: hh")
|
||||
t.Assert(err.Strings(), g.Slice{"InvalidRules: hh"})
|
||||
t.Assert(err.FirstString(), "InvalidRules: hh")
|
||||
t.Assert(err.FirstError(), "InvalidRules: hh")
|
||||
t.Assert(gerror.Current(err), "InvalidRules: hh")
|
||||
})
|
||||
}
|
||||
|
@ -12,29 +12,27 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gvalid"
|
||||
)
|
||||
|
||||
func Test_CustomRule1(t *testing.T) {
|
||||
rule := "custom"
|
||||
err := gvalid.RegisterRule(
|
||||
gvalid.RegisterRule(
|
||||
rule,
|
||||
func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
pass := gconv.String(value)
|
||||
func(ctx context.Context, in gvalid.RuleFuncInput) error {
|
||||
pass := in.Value.String()
|
||||
if len(pass) != 6 {
|
||||
return errors.New(message)
|
||||
return errors.New(in.Message)
|
||||
}
|
||||
m := gconv.Map(data)
|
||||
m := in.Data.Map()
|
||||
if m["data"] != pass {
|
||||
return errors.New(message)
|
||||
return errors.New(in.Message)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gvalid.CheckValue(context.TODO(), "123456", rule, "custom message")
|
||||
t.Assert(err.String(), "custom message")
|
||||
@ -71,14 +69,13 @@ func Test_CustomRule1(t *testing.T) {
|
||||
|
||||
func Test_CustomRule2(t *testing.T) {
|
||||
rule := "required-map"
|
||||
err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
m := gconv.Map(value)
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
|
||||
m := in.Value.Map()
|
||||
if len(m) == 0 {
|
||||
return errors.New(message)
|
||||
return errors.New(in.Message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Check.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
errStr := "data map should not be empty"
|
||||
@ -115,14 +112,13 @@ func Test_CustomRule2(t *testing.T) {
|
||||
|
||||
func Test_CustomRule_AllowEmpty(t *testing.T) {
|
||||
rule := "allow-empty-str"
|
||||
err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
s := gconv.String(value)
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
|
||||
s := in.Value.String()
|
||||
if len(s) == 0 || s == "gf" {
|
||||
return nil
|
||||
}
|
||||
return errors.New(message)
|
||||
return errors.New(in.Message)
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Check.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
errStr := "error"
|
||||
@ -160,13 +156,13 @@ func Test_CustomRule_AllowEmpty(t *testing.T) {
|
||||
|
||||
func TestValidator_RuleFunc(t *testing.T) {
|
||||
ruleName := "custom_1"
|
||||
ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
pass := gconv.String(value)
|
||||
ruleFunc := func(ctx context.Context, in gvalid.RuleFuncInput) error {
|
||||
pass := in.Value.String()
|
||||
if len(pass) != 6 {
|
||||
return errors.New(message)
|
||||
return errors.New(in.Message)
|
||||
}
|
||||
if m := gconv.Map(data); m["data"] != pass {
|
||||
return errors.New(message)
|
||||
if m := in.Data.Map(); m["data"] != pass {
|
||||
return errors.New(in.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -214,13 +210,13 @@ func TestValidator_RuleFunc(t *testing.T) {
|
||||
|
||||
func TestValidator_RuleFuncMap(t *testing.T) {
|
||||
ruleName := "custom_1"
|
||||
ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
pass := gconv.String(value)
|
||||
ruleFunc := func(ctx context.Context, in gvalid.RuleFuncInput) error {
|
||||
pass := in.Value.String()
|
||||
if len(pass) != 6 {
|
||||
return errors.New(message)
|
||||
return errors.New(in.Message)
|
||||
}
|
||||
if m := gconv.Map(data); m["data"] != pass {
|
||||
return errors.New(message)
|
||||
if m := in.Data.Map(); m["data"] != pass {
|
||||
return errors.New(in.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -35,9 +35,8 @@ func Test_FirstString(t *testing.T) {
|
||||
rule = "ipv4"
|
||||
val = "0.0.0"
|
||||
err = gvalid.CheckValue(context.TODO(), val, rule, nil)
|
||||
n = err.FirstString()
|
||||
)
|
||||
t.Assert(n, "The value must be a valid IPv4 address")
|
||||
t.Assert(err.FirstError(), "The value must be a valid IPv4 address")
|
||||
})
|
||||
}
|
||||
|
||||
@ -52,12 +51,12 @@ func Test_CustomError1(t *testing.T) {
|
||||
t.Error("规则校验失败")
|
||||
} else {
|
||||
if v, ok := e.Map()["integer"]; ok {
|
||||
if strings.Compare(v, msgs["integer"]) != 0 {
|
||||
if strings.Compare(v.Error(), msgs["integer"]) != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
}
|
||||
if v, ok := e.Map()["length"]; ok {
|
||||
if strings.Compare(v, msgs["length"]) != 0 {
|
||||
if strings.Compare(v.Error(), msgs["length"]) != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
}
|
||||
@ -72,12 +71,12 @@ func Test_CustomError2(t *testing.T) {
|
||||
t.Error("规则校验失败")
|
||||
} else {
|
||||
if v, ok := e.Map()["integer"]; ok {
|
||||
if strings.Compare(v, "请输入一个整数") != 0 {
|
||||
if strings.Compare(v.Error(), "请输入一个整数") != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
}
|
||||
if v, ok := e.Map()["length"]; ok {
|
||||
if strings.Compare(v, "参数长度不对啊老铁") != 0 {
|
||||
if strings.Compare(v.Error(), "参数长度不对啊老铁") != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user