add ut cases for command gen dao (#2958)

This commit is contained in:
John Guo 2023-09-12 22:00:35 +08:00 committed by GitHub
parent 5059abd88e
commit 5219c5c37e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1121 additions and 250 deletions

View File

@ -1,9 +1,15 @@
#!/usr/bin/env bash
# find all path that contains go.mod.
for file in `find . -name go.mod`; do
dirpath=$(dirname $file)
echo $dirpath
if [[ $file =~ "/testdata/" ]]; then
echo "ignore testdata path $file"
continue 1
fi
# package kuhecm needs golang >= v1.18
if [ "kubecm" = $(basename $dirpath) ]; then
if ! go version|grep -q "1.19"; then

View File

@ -1,7 +1,12 @@
// Copyright GoFrame gf 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 cmd
import (
"fmt"
"testing"
"github.com/gogf/gf/v2/test/gtest"
@ -10,11 +15,10 @@ import (
func Test_Fix_doFixV25Content(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
content = gtest.DataContent(`fix25_content.go.txt`)
content = gtest.DataContent(`fix`, `fix25_content.go`)
f = cFix{}
)
newContent, err := f.doFixV25Content(content)
_, err := f.doFixV25Content(content)
t.AssertNil(err)
fmt.Println(newContent)
})
}

View File

@ -0,0 +1,229 @@
// Copyright GoFrame gf 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 cmd
import (
"context"
"fmt"
"testing"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/gendao"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/guid"
"github.com/gogf/gf/v2/util/gutil"
)
var ctx = context.Background()
func dropTableWithDb(db gdb.DB, table string) {
dropTableStmt := fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)
if _, err := db.Exec(ctx, dropTableStmt); err != nil {
gtest.Error(err)
}
}
func Test_Gen_Dao_Default(t *testing.T) {
link := "mysql:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true"
db, err := gdb.New(gdb.ConfigNode{
Link: link,
})
gtest.AssertNil(err)
gtest.C(t, func(t *gtest.T) {
var (
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`gendao`, `user.tpl.sql`),
table,
)
)
dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link,
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "SnakeScreaming",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: false,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
// for go mod import path auto retrieve.
err = gfile.Copy(
gtest.DataPath("gendao", "go.mod.txt"),
gfile.Join(path, "go.mod"),
)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
path + "/dao/internal/table_user.go",
path + "/dao/table_user.go",
path + "/model/do/table_user.go",
path + "/model/entity/table_user.go",
})
// content
testPath := gtest.DataPath("gendao", "generated_user")
expectFiles := []string{
testPath + "/dao/internal/table_user.go",
testPath + "/dao/table_user.go",
testPath + "/model/do/table_user.go",
testPath + "/model/entity/table_user.go",
}
for i, _ := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}
func Test_Gen_Dao_TypeMapping(t *testing.T) {
link := "mysql:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true"
db, err := gdb.New(gdb.ConfigNode{
Link: link,
})
gtest.AssertNil(err)
gtest.C(t, func(t *gtest.T) {
var (
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`gendao`, `user.tpl.sql`),
table,
)
)
defer dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link,
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: false,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: map[gendao.DBFieldTypeName]gendao.CustomAttributeType{
"int": {
Type: "int64",
Import: "",
},
"decimal": {
Type: "decimal.Decimal",
Import: "github.com/shopspring/decimal",
},
},
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
// for go mod import path auto retrieve.
err = gfile.Copy(
gtest.DataPath("gendao", "go.mod.txt"),
gfile.Join(path, "go.mod"),
)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
// files
files, err := gfile.ScanDir(path, "*.go", true)
t.AssertNil(err)
t.Assert(files, []string{
path + "/dao/internal/table_user.go",
path + "/dao/table_user.go",
path + "/model/do/table_user.go",
path + "/model/entity/table_user.go",
})
// content
testPath := gtest.DataPath("gendao", "generated_user_type_mapping")
expectFiles := []string{
testPath + "/dao/internal/table_user.go",
testPath + "/dao/table_user.go",
testPath + "/model/do/table_user.go",
testPath + "/model/entity/table_user.go",
}
for i, _ := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}

View File

@ -115,7 +115,7 @@ generated json tag case for model struct, cases are as follows:
var (
createdAt = gtime.Now()
defaultTypeMapping = map[string]TypeMapping{
defaultTypeMapping = map[DBFieldTypeName]CustomAttributeType{
"decimal": {
Type: "float64",
},
@ -172,31 +172,32 @@ type (
CGenDao struct{}
CGenDaoInput struct {
g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"`
Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
TypeMapping map[string]TypeMapping `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
}
CGenDaoOutput struct{}
@ -207,7 +208,8 @@ type (
NewTableNames []string
}
TypeMapping struct {
DBFieldTypeName = string
CustomAttributeType struct {
Type string `brief:"custom attribute type name"`
Import string `brief:"custom import for this type"`
}

View File

@ -96,7 +96,7 @@ func generateStructFieldDefinition(
if err != nil {
panic(err)
}
localTypeNameStr = string(localTypeName)
switch localTypeName {
case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
if in.StdTime {

View File

@ -27,4 +27,4 @@ func Test_Router_Hook_Multi(t *testing.T) {
r.Response.Write("2")
},
})
}
}

View File

@ -0,0 +1,85 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// TableUserDao is the data access object for table table_user.
type TableUserDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of current DAO.
columns TableUserColumns // columns contains all the column names of Table for convenient usage.
}
// TableUserColumns defines and stores column names for table table_user.
type TableUserColumns struct {
Id string // User ID
Passport string // User Passport
Password string // User Password
Nickname string // User Nickname
Score string // Total score amount.
CreateAt string // Created Time
UpdateAt string // Updated Time
}
// tableUserColumns holds the columns for table table_user.
var tableUserColumns = TableUserColumns{
Id: "id",
Passport: "passport",
Password: "password",
Nickname: "nickname",
Score: "score",
CreateAt: "create_at",
UpdateAt: "update_at",
}
// NewTableUserDao creates and returns a new DAO object for table data access.
func NewTableUserDao() *TableUserDao {
return &TableUserDao{
group: "test",
table: "table_user",
columns: tableUserColumns,
}
}
// DB retrieves and returns the underlying raw database management object of current DAO.
func (dao *TableUserDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of current dao.
func (dao *TableUserDao) Table() string {
return dao.table
}
// Columns returns all column names of current dao.
func (dao *TableUserDao) Columns() TableUserColumns {
return dao.columns
}
// Group returns the configuration group name of database of current dao.
func (dao *TableUserDao) Group() string {
return dao.group
}
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
func (dao *TableUserDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function f
// as it is automatically handled by this function.
func (dao *TableUserDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}

View File

@ -0,0 +1,27 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package dao
import (
"for-gendao-test/pkg/dao/internal"
)
// internalTableUserDao is internal type for wrapping internal DAO implements.
type internalTableUserDao = *internal.TableUserDao
// tableUserDao is the data access object for table table_user.
// You can define custom methods on it to extend its functionality as you wish.
type tableUserDao struct {
internalTableUserDao
}
var (
// TableUser is globally public accessible object for table table_user operations.
TableUser = tableUserDao{
internal.NewTableUserDao(),
}
)
// Fill with you ideas below.

View File

@ -0,0 +1,22 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TableUser is the golang structure of table table_user for DAO operations like Where/Data.
type TableUser struct {
g.Meta `orm:"table:table_user, do:true"`
Id interface{} // User ID
Passport interface{} // User Passport
Password interface{} // User Password
Nickname interface{} // User Nickname
Score interface{} // Total score amount.
CreateAt *gtime.Time // Created Time
UpdateAt *gtime.Time // Updated Time
}

View File

@ -0,0 +1,20 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// TableUser is the golang structure for table table_user.
type TableUser struct {
Id uint `json:"ID" ` // User ID
Passport string `json:"PASSPORT" ` // User Passport
Password string `json:"PASSWORD" ` // User Password
Nickname string `json:"NICKNAME" ` // User Nickname
Score float64 `json:"SCORE" ` // Total score amount.
CreateAt *gtime.Time `json:"CREATE_AT" ` // Created Time
UpdateAt *gtime.Time `json:"UPDATE_AT" ` // Updated Time
}

View File

@ -0,0 +1,85 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// TableUserDao is the data access object for table table_user.
type TableUserDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of current DAO.
columns TableUserColumns // columns contains all the column names of Table for convenient usage.
}
// TableUserColumns defines and stores column names for table table_user.
type TableUserColumns struct {
Id string // User ID
Passport string // User Passport
Password string // User Password
Nickname string // User Nickname
Score string // Total score amount.
CreateAt string // Created Time
UpdateAt string // Updated Time
}
// tableUserColumns holds the columns for table table_user.
var tableUserColumns = TableUserColumns{
Id: "id",
Passport: "passport",
Password: "password",
Nickname: "nickname",
Score: "score",
CreateAt: "create_at",
UpdateAt: "update_at",
}
// NewTableUserDao creates and returns a new DAO object for table data access.
func NewTableUserDao() *TableUserDao {
return &TableUserDao{
group: "test",
table: "table_user",
columns: tableUserColumns,
}
}
// DB retrieves and returns the underlying raw database management object of current DAO.
func (dao *TableUserDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of current dao.
func (dao *TableUserDao) Table() string {
return dao.table
}
// Columns returns all column names of current dao.
func (dao *TableUserDao) Columns() TableUserColumns {
return dao.columns
}
// Group returns the configuration group name of database of current dao.
func (dao *TableUserDao) Group() string {
return dao.group
}
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
func (dao *TableUserDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function f
// as it is automatically handled by this function.
func (dao *TableUserDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}

View File

@ -0,0 +1,27 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package dao
import (
"for-gendao-test/pkg/dao/internal"
)
// internalTableUserDao is internal type for wrapping internal DAO implements.
type internalTableUserDao = *internal.TableUserDao
// tableUserDao is the data access object for table table_user.
// You can define custom methods on it to extend its functionality as you wish.
type tableUserDao struct {
internalTableUserDao
}
var (
// TableUser is globally public accessible object for table table_user operations.
TableUser = tableUserDao{
internal.NewTableUserDao(),
}
)
// Fill with you ideas below.

View File

@ -0,0 +1,22 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TableUser is the golang structure of table table_user for DAO operations like Where/Data.
type TableUser struct {
g.Meta `orm:"table:table_user, do:true"`
Id interface{} // User ID
Passport interface{} // User Passport
Password interface{} // User Password
Nickname interface{} // User Nickname
Score interface{} // Total score amount.
CreateAt *gtime.Time // Created Time
UpdateAt *gtime.Time // Updated Time
}

View File

@ -0,0 +1,21 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
"github.com/shopspring/decimal"
)
// TableUser is the golang structure for table table_user.
type TableUser struct {
Id int64 `json:"id" ` // User ID
Passport string `json:"passport" ` // User Passport
Password string `json:"password" ` // User Password
Nickname string `json:"nickname" ` // User Nickname
Score decimal.Decimal `json:"score" ` // Total score amount.
CreateAt *gtime.Time `json:"createAt" ` // Created Time
UpdateAt *gtime.Time `json:"updateAt" ` // Updated Time
}

View File

@ -0,0 +1,32 @@
module for-gendao-test/pkg
go 1.18
require (
github.com/gogf/gf/v2 v2.5.3
github.com/shopspring/decimal v1.3.1
)
require (
github.com/BurntSushi/toml v1.2.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grokify/html-strip-tags-go v0.0.1 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -0,0 +1,10 @@
CREATE TABLE `%s` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
`passport` varchar(45) NOT NULL COMMENT 'User Passport',
`password` varchar(45) NOT NULL COMMENT 'User Password',
`nickname` varchar(45) NOT NULL COMMENT 'User Nickname',
`score` decimal(10,2) unsigned DEFAULT NULL COMMENT 'Total score amount.',
`create_at` datetime DEFAULT NULL COMMENT 'Created Time',
`update_at` datetime DEFAULT NULL COMMENT 'Updated Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View File

@ -22,16 +22,25 @@ import (
func Test_SetSingleCustomListener(t *testing.T) {
var (
p, _ = gtcp.GetFreePort()
ln, _ = net.Listen("tcp", fmt.Sprintf(":%d", p))
s = g.Server(guid.S())
p1 int
ln1 net.Listener
)
for i := 0; i < 1000; i++ {
p1, _ = gtcp.GetFreePort()
if ln1 == nil {
ln1, _ = net.Listen("tcp", fmt.Sprintf(":%d", p1))
}
if ln1 != nil {
break
}
}
s := g.Server(guid.S())
s.Group("/", func(group *ghttp.RouterGroup) {
group.GET("/test", func(r *ghttp.Request) {
r.Response.Write("test")
})
})
err := s.SetListener(ln)
err := s.SetListener(ln1)
gtest.AssertNil(err)
s.Start()
@ -51,12 +60,25 @@ func Test_SetSingleCustomListener(t *testing.T) {
}
func Test_SetMultipleCustomListeners(t *testing.T) {
p1, _ := gtcp.GetFreePort()
p2, _ := gtcp.GetFreePort()
ln1, _ := net.Listen("tcp", fmt.Sprintf(":%d", p1))
ln2, _ := net.Listen("tcp", fmt.Sprintf(":%d", p2))
var (
p1 int
p2 int
ln1 net.Listener
ln2 net.Listener
)
for i := 0; i < 1000; i++ {
p1, _ = gtcp.GetFreePort()
p2, _ = gtcp.GetFreePort()
if ln1 == nil {
ln1, _ = net.Listen("tcp", fmt.Sprintf(":%d", p1))
}
if ln2 == nil {
ln2, _ = net.Listen("tcp", fmt.Sprintf(":%d", p2))
}
if ln1 != nil && ln2 != nil {
break
}
}
s := g.Server(guid.S())
s.Group("/", func(group *ghttp.RouterGroup) {
group.GET("/test", func(r *ghttp.Request) {

View File

@ -64,7 +64,7 @@ func Mkdir(path string) (err error) {
return nil
}
// Create creates file with given `path` recursively.
// Create creates a file with given `path` recursively.
// The parameter `path` is suggested to be absolute path.
func Create(path string) (*os.File, error) {
dir := Dir(path)

View File

@ -15,21 +15,74 @@ import (
"github.com/gogf/gf/v2/errors/gerror"
)
// CopyOption is the option for Copy* functions.
type CopyOption struct {
Sync bool // Auto call file sync after source file content copied to target file.
Mode os.FileMode // Destination created file mode. The default file mode is DefaultPermCopy.
}
// Copy file/directory from `src` to `dst`.
//
// If `src` is file, it calls CopyFile to implements copy feature,
// or else it calls CopyDir.
func Copy(src string, dst string) error {
//
// If `src` is file, but `dst` already exists and is a folder,
// it then creates a same name file of `src` in folder `dst`.
//
// Eg:
// Copy("/tmp/file1", "/tmp/file2") => /tmp/file1 copied to /tmp/file2
// Copy("/tmp/dir1", "/tmp/dir2") => /tmp/dir1 copied to /tmp/dir2
// Copy("/tmp/file1", "/tmp/dir2") => /tmp/file1 copied to /tmp/dir2/file1
// Copy("/tmp/dir1", "/tmp/file2") => error
func Copy(src string, dst string, option ...CopyOption) error {
if src == "" {
return gerror.NewCode(gcode.CodeInvalidParameter, "source path cannot be empty")
}
if dst == "" {
return gerror.NewCode(gcode.CodeInvalidParameter, "destination path cannot be empty")
}
if IsFile(src) {
return CopyFile(src, dst)
srcStat, srcStatErr := os.Stat(src)
if srcStatErr != nil {
if os.IsNotExist(srcStatErr) {
return gerror.WrapCodef(
gcode.CodeInvalidParameter,
srcStatErr,
`the src path "%s" does not exist`,
src,
)
}
return gerror.WrapCodef(
gcode.CodeInternalError, srcStatErr, `call os.Stat on "%s" failed`, src,
)
}
return CopyDir(src, dst)
dstStat, dstStatErr := os.Stat(dst)
if dstStatErr != nil && !os.IsNotExist(dstStatErr) {
return gerror.WrapCodef(
gcode.CodeInternalError, dstStatErr, `call os.Stat on "%s" failed`, dst)
}
if IsFile(src) {
var isDstExist = false
if dstStat != nil && !os.IsNotExist(dstStatErr) {
isDstExist = true
}
if isDstExist && dstStat.IsDir() {
var (
srcName = Basename(src)
dstPath = Join(dst, srcName)
)
return CopyFile(src, dstPath, option...)
}
return CopyFile(src, dst, option...)
}
if !srcStat.IsDir() && dstStat != nil && dstStat.IsDir() {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`Copy failed: the src path "%s" is file, but the dst path "%s" is folder`,
src, dst,
)
}
return CopyDir(src, dst, option...)
}
// CopyFile copies the contents of the file named `src` to the file named
@ -38,7 +91,8 @@ func Copy(src string, dst string) error {
// of the source file. The file mode will be copied from the source and
// the copied data is synced/flushed to stable storage.
// Thanks: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
func CopyFile(src, dst string) (err error) {
func CopyFile(src, dst string, option ...CopyOption) (err error) {
var usedOption = getCopyOption(option...)
if src == "" {
return gerror.NewCode(gcode.CodeInvalidParameter, "source file cannot be empty")
}
@ -49,6 +103,35 @@ func CopyFile(src, dst string) (err error) {
if src == dst {
return nil
}
// file state check.
srcStat, srcStatErr := os.Stat(src)
if srcStatErr != nil {
if os.IsNotExist(srcStatErr) {
return gerror.WrapCodef(
gcode.CodeInvalidParameter,
srcStatErr,
`the src path "%s" does not exist`,
src,
)
}
return gerror.WrapCodef(
gcode.CodeInternalError, srcStatErr, `call os.Stat on "%s" failed`, src,
)
}
dstStat, dstStatErr := os.Stat(dst)
if dstStatErr != nil && !os.IsNotExist(dstStatErr) {
return gerror.WrapCodef(
gcode.CodeInternalError, dstStatErr, `call os.Stat on "%s" failed`, dst,
)
}
if !srcStat.IsDir() && dstStat != nil && dstStat.IsDir() {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`CopyFile failed: the src path "%s" is file, but the dst path "%s" is folder`,
src, dst,
)
}
// copy file logic.
var inFile *os.File
inFile, err = Open(src)
if err != nil {
@ -73,11 +156,13 @@ func CopyFile(src, dst string) (err error) {
err = gerror.Wrapf(err, `io.Copy failed from "%s" to "%s"`, src, dst)
return
}
if err = outFile.Sync(); err != nil {
err = gerror.Wrapf(err, `file sync failed for file "%s"`, dst)
return
if usedOption.Sync {
if err = outFile.Sync(); err != nil {
err = gerror.Wrapf(err, `file sync failed for file "%s"`, dst)
return
}
}
if err = Chmod(dst, DefaultPermCopy); err != nil {
if err = Chmod(dst, usedOption.Mode); err != nil {
return
}
return
@ -86,7 +171,8 @@ func CopyFile(src, dst string) (err error) {
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
//
// Note that, the Source directory must exist and symlinks are ignored and skipped.
func CopyDir(src string, dst string) (err error) {
func CopyDir(src string, dst string, option ...CopyOption) (err error) {
var usedOption = getCopyOption(option...)
if src == "" {
return gerror.NewCode(gcode.CodeInvalidParameter, "source directory cannot be empty")
}
@ -107,8 +193,13 @@ func CopyDir(src string, dst string) (err error) {
return gerror.NewCode(gcode.CodeInvalidParameter, "source is not a directory")
}
if !Exists(dst) {
if err = os.MkdirAll(dst, DefaultPermCopy); err != nil {
err = gerror.Wrapf(err, `create directory failed for path "%s", perm "%s"`, dst, DefaultPermCopy)
if err = os.MkdirAll(dst, usedOption.Mode); err != nil {
err = gerror.Wrapf(
err,
`create directory failed for path "%s", perm "%s"`,
dst,
usedOption.Mode,
)
return
}
}
@ -129,10 +220,21 @@ func CopyDir(src string, dst string) (err error) {
if entry.Type()&os.ModeSymlink != 0 {
continue
}
if err = CopyFile(srcPath, dstPath); err != nil {
if err = CopyFile(srcPath, dstPath, option...); err != nil {
return
}
}
}
return
}
func getCopyOption(option ...CopyOption) CopyOption {
var usedOption CopyOption
if len(option) > 0 {
usedOption = option[0]
}
if usedOption.Mode == 0 {
usedOption.Mode = DefaultPermCopy
}
return usedOption
}

View File

@ -12,6 +12,7 @@ import (
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/guid"
)
func Test_Copy(t *testing.T) {
@ -33,6 +34,40 @@ func Test_Copy(t *testing.T) {
})
}
func Test_Copy_File_To_Dir(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
src = gtest.DataPath("dir1", "file1")
dst = gfile.Temp(guid.S(), "dir2")
)
err := gfile.Mkdir(dst)
t.AssertNil(err)
defer gfile.Remove(dst)
err = gfile.Copy(src, dst)
t.AssertNil(err)
expectPath := gfile.Join(dst, "file1")
t.Assert(gfile.GetContents(expectPath), gfile.GetContents(src))
})
}
func Test_Copy_Dir_To_File(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
src = gtest.DataPath("dir1")
dst = gfile.Temp(guid.S(), "file2")
)
f, err := gfile.Create(dst)
t.AssertNil(err)
defer f.Close()
defer gfile.Remove(dst)
err = gfile.Copy(src, dst)
t.AssertNE(err, nil)
})
}
func Test_CopyFile(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (

View File

@ -0,0 +1 @@
file1

View File

@ -0,0 +1 @@
file2

View File

@ -9,6 +9,8 @@ package gstructs
import (
"reflect"
"github.com/gogf/gf/v2/errors/gerror"
)
// Type wraps reflect.Type for additional features.
@ -25,13 +27,15 @@ type Field struct {
TagName string
// Retrieved tag value.
// There might be more than one tags in the field, but only one can be retrieved according to calling function rules.
// There might be more than one tags in the field,
// but only one can be retrieved according to calling function rules.
TagValue string
}
// FieldsInput is the input parameter struct type for function Fields.
type FieldsInput struct {
// Pointer should be type of struct/*struct.
// TODO this attribute name is not suitable, which would make confuse.
Pointer interface{}
// RecursiveOption specifies the way retrieving the fields recursively if the attribute
@ -42,6 +46,7 @@ type FieldsInput struct {
// FieldMapInput is the input parameter struct type for function FieldMap.
type FieldMapInput struct {
// Pointer should be type of struct/*struct.
// TODO this attribute name is not suitable, which would make confuse.
Pointer interface{}
// PriorityTagArray specifies the priority tag array for retrieving from high to low.
@ -60,3 +65,188 @@ const (
RecursiveOptionEmbedded RecursiveOption = 1 // Recursively retrieving fields as map if the field is an embedded struct.
RecursiveOptionEmbeddedNoTag RecursiveOption = 2 // Recursively retrieving fields as map if the field is an embedded struct and the field has no tag.
)
// Fields retrieves and returns the fields of `pointer` as slice.
func Fields(in FieldsInput) ([]Field, error) {
var (
ok bool
fieldFilterMap = make(map[string]struct{})
retrievedFields = make([]Field, 0)
currentLevelFieldMap = make(map[string]Field)
rangeFields, err = getFieldValues(in.Pointer)
)
if err != nil {
return nil, err
}
for index := 0; index < len(rangeFields); index++ {
field := rangeFields[index]
currentLevelFieldMap[field.Name()] = field
}
for index := 0; index < len(rangeFields); index++ {
field := rangeFields[index]
if _, ok = fieldFilterMap[field.Name()]; ok {
continue
}
if field.IsEmbedded() {
if in.RecursiveOption != RecursiveOptionNone {
switch in.RecursiveOption {
case RecursiveOptionEmbeddedNoTag:
if field.TagStr() != "" {
break
}
fallthrough
case RecursiveOptionEmbedded:
structFields, err := Fields(FieldsInput{
Pointer: field.Value,
RecursiveOption: in.RecursiveOption,
})
if err != nil {
return nil, err
}
// The current level fields can overwrite the sub-struct fields with the same name.
for i := 0; i < len(structFields); i++ {
var (
structField = structFields[i]
fieldName = structField.Name()
)
if _, ok = fieldFilterMap[fieldName]; ok {
continue
}
fieldFilterMap[fieldName] = struct{}{}
if v, ok := currentLevelFieldMap[fieldName]; !ok {
retrievedFields = append(retrievedFields, structField)
} else {
retrievedFields = append(retrievedFields, v)
}
}
continue
}
}
continue
}
fieldFilterMap[field.Name()] = struct{}{}
retrievedFields = append(retrievedFields, field)
}
return retrievedFields, nil
}
// FieldMap retrieves and returns struct field as map[name/tag]Field from `pointer`.
//
// The parameter `pointer` should be type of struct/*struct.
//
// The parameter `priority` specifies the priority tag array for retrieving from high to low.
// If it's given `nil`, it returns map[name]Field, of which the `name` is attribute name.
//
// The parameter `recursive` specifies whether retrieving the fields recursively if the attribute
// is an embedded struct.
//
// Note that it only retrieves the exported attributes with first letter upper-case from struct.
func FieldMap(in FieldMapInput) (map[string]Field, error) {
fields, err := getFieldValues(in.Pointer)
if err != nil {
return nil, err
}
var (
tagValue string
mapField = make(map[string]Field)
)
for _, field := range fields {
// Only retrieve exported attributes.
if !field.IsExported() {
continue
}
tagValue = ""
for _, p := range in.PriorityTagArray {
tagValue = field.Tag(p)
if tagValue != "" && tagValue != "-" {
break
}
}
tempField := field
tempField.TagValue = tagValue
if tagValue != "" {
mapField[tagValue] = tempField
} else {
if in.RecursiveOption != RecursiveOptionNone && field.IsEmbedded() {
switch in.RecursiveOption {
case RecursiveOptionEmbeddedNoTag:
if field.TagStr() != "" {
mapField[field.Name()] = tempField
break
}
fallthrough
case RecursiveOptionEmbedded:
m, err := FieldMap(FieldMapInput{
Pointer: field.Value,
PriorityTagArray: in.PriorityTagArray,
RecursiveOption: in.RecursiveOption,
})
if err != nil {
return nil, err
}
for k, v := range m {
if _, ok := mapField[k]; !ok {
tempV := v
mapField[k] = tempV
}
}
}
} else {
mapField[field.Name()] = tempField
}
}
}
return mapField, nil
}
// StructType retrieves and returns the struct Type of specified struct/*struct.
// The parameter `object` should be either type of struct/*struct/[]struct/[]*struct.
func StructType(object interface{}) (*Type, error) {
var (
reflectValue reflect.Value
reflectKind reflect.Kind
reflectType reflect.Type
)
if rv, ok := object.(reflect.Value); ok {
reflectValue = rv
} else {
reflectValue = reflect.ValueOf(object)
}
reflectKind = reflectValue.Kind()
for {
switch reflectKind {
case reflect.Ptr:
if !reflectValue.IsValid() || reflectValue.IsNil() {
// If pointer is type of *struct and nil, then automatically create a temporary struct.
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
reflectKind = reflectValue.Kind()
} else {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
case reflect.Array, reflect.Slice:
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
reflectKind = reflectValue.Kind()
default:
goto exitLoop
}
}
exitLoop:
if reflectKind != reflect.Struct {
return nil, gerror.Newf(
`invalid object kind "%s", kind of "struct" is required`,
reflectKind,
)
}
reflectType = reflectValue.Type()
return &Type{
Type: reflectType,
}, nil
}

View File

@ -9,6 +9,7 @@ package gstructs
import (
"reflect"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/util/gtag"
)
@ -94,139 +95,12 @@ func (f *Field) OriginalKind() reflect.Kind {
return reflectKind
}
// Fields retrieves and returns the fields of `pointer` as slice.
func Fields(in FieldsInput) ([]Field, error) {
var (
ok bool
fieldFilterMap = make(map[string]struct{})
retrievedFields = make([]Field, 0)
currentLevelFieldMap = make(map[string]Field)
)
rangeFields, err := getFieldValues(in.Pointer)
if err != nil {
return nil, err
}
for index := 0; index < len(rangeFields); index++ {
field := rangeFields[index]
currentLevelFieldMap[field.Name()] = field
}
for index := 0; index < len(rangeFields); index++ {
field := rangeFields[index]
if _, ok = fieldFilterMap[field.Name()]; ok {
continue
}
if field.IsEmbedded() {
if in.RecursiveOption != RecursiveOptionNone {
switch in.RecursiveOption {
case RecursiveOptionEmbeddedNoTag:
if field.TagStr() != "" {
break
}
fallthrough
case RecursiveOptionEmbedded:
structFields, err := Fields(FieldsInput{
Pointer: field.Value,
RecursiveOption: in.RecursiveOption,
})
if err != nil {
return nil, err
}
// The current level fields can overwrite the sub-struct fields with the same name.
for i := 0; i < len(structFields); i++ {
var (
structField = structFields[i]
fieldName = structField.Name()
)
if _, ok = fieldFilterMap[fieldName]; ok {
continue
}
fieldFilterMap[fieldName] = struct{}{}
if v, ok := currentLevelFieldMap[fieldName]; !ok {
retrievedFields = append(retrievedFields, structField)
} else {
retrievedFields = append(retrievedFields, v)
}
}
continue
}
}
continue
}
fieldFilterMap[field.Name()] = struct{}{}
retrievedFields = append(retrievedFields, field)
}
return retrievedFields, nil
// IsEmpty checks and returns whether the value of this Field is empty.
func (f *Field) IsEmpty() bool {
return empty.IsEmpty(f.Value)
}
// FieldMap retrieves and returns struct field as map[name/tag]Field from `pointer`.
//
// The parameter `pointer` should be type of struct/*struct.
//
// The parameter `priority` specifies the priority tag array for retrieving from high to low.
// If it's given `nil`, it returns map[name]Field, of which the `name` is attribute name.
//
// The parameter `recursive` specifies the whether retrieving the fields recursively if the attribute
// is an embedded struct.
//
// Note that it only retrieves the exported attributes with first letter upper-case from struct.
func FieldMap(in FieldMapInput) (map[string]Field, error) {
fields, err := getFieldValues(in.Pointer)
if err != nil {
return nil, err
}
var (
tagValue string
mapField = make(map[string]Field)
)
for _, field := range fields {
// Only retrieve exported attributes.
if !field.IsExported() {
continue
}
tagValue = ""
for _, p := range in.PriorityTagArray {
tagValue = field.Tag(p)
if tagValue != "" && tagValue != "-" {
break
}
}
tempField := field
tempField.TagValue = tagValue
if tagValue != "" {
mapField[tagValue] = tempField
} else {
if in.RecursiveOption != RecursiveOptionNone && field.IsEmbedded() {
switch in.RecursiveOption {
case RecursiveOptionEmbeddedNoTag:
if field.TagStr() != "" {
mapField[field.Name()] = tempField
break
}
fallthrough
case RecursiveOptionEmbedded:
m, err := FieldMap(FieldMapInput{
Pointer: field.Value,
PriorityTagArray: in.PriorityTagArray,
RecursiveOption: in.RecursiveOption,
})
if err != nil {
return nil, err
}
for k, v := range m {
if _, ok := mapField[k]; !ok {
tempV := v
mapField[k] = tempV
}
}
}
} else {
mapField[field.Name()] = tempField
}
}
}
return mapField, nil
// IsNil checks and returns whether the value of this Field is nil.
func (f *Field) IsNil(traceSource ...bool) bool {
return empty.IsNil(f.Value, traceSource...)
}

View File

@ -120,16 +120,16 @@ func TagMapField(object interface{}, priority []string) (map[string]Field, error
return tagMap, nil
}
func getFieldValues(value interface{}) ([]Field, error) {
func getFieldValues(structObject interface{}) ([]Field, error) {
var (
reflectValue reflect.Value
reflectKind reflect.Kind
)
if v, ok := value.(reflect.Value); ok {
if v, ok := structObject.(reflect.Value); ok {
reflectValue = v
reflectKind = reflectValue.Kind()
} else {
reflectValue = reflect.ValueOf(value)
reflectValue = reflect.ValueOf(structObject)
reflectKind = reflectValue.Kind()
}
for {

View File

@ -6,60 +6,6 @@
package gstructs
import (
"reflect"
"github.com/gogf/gf/v2/errors/gerror"
)
// StructType retrieves and returns the struct Type of specified struct/*struct.
// The parameter `object` should be either type of struct/*struct/[]struct/[]*struct.
func StructType(object interface{}) (*Type, error) {
var (
reflectValue reflect.Value
reflectKind reflect.Kind
reflectType reflect.Type
)
if rv, ok := object.(reflect.Value); ok {
reflectValue = rv
} else {
reflectValue = reflect.ValueOf(object)
}
reflectKind = reflectValue.Kind()
for {
switch reflectKind {
case reflect.Ptr:
if !reflectValue.IsValid() || reflectValue.IsNil() {
// If pointer is type of *struct and nil, then automatically create a temporary struct.
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
reflectKind = reflectValue.Kind()
} else {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
case reflect.Array, reflect.Slice:
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
reflectKind = reflectValue.Kind()
default:
goto exitLoop
}
}
exitLoop:
if reflectKind != reflect.Struct {
return nil, gerror.Newf(
`invalid object kind "%s", kind of "struct" is required`,
reflectKind,
)
}
reflectType = reflectValue.Type()
return &Type{
Type: reflectType,
}, nil
}
// Signature returns a unique string as this type.
func (t Type) Signature() string {
return t.PkgPath() + "/" + t.String()

View File

@ -26,6 +26,25 @@ func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{
})
}
// ConvertWithRefer converts the variable `fromValue` to the type referred by value `referValue`.
//
// The optional parameter `extraParams` is used for additional necessary parameter for this conversion.
// It supports common basic types conversion as its conversion based on type name string.
func ConvertWithRefer(fromValue interface{}, referValue interface{}, extraParams ...interface{}) interface{} {
var referValueRf reflect.Value
if v, ok := referValue.(reflect.Value); ok {
referValueRf = v
} else {
referValueRf = reflect.ValueOf(referValue)
}
return doConvert(doConvertInput{
FromValue: fromValue,
ToTypeName: referValueRf.Type().String(),
ReferValue: referValue,
Extra: extraParams,
})
}
type doConvertInput struct {
FromValue interface{} // Value that is converted from.
ToTypeName string // Target value type name in string.

View File

@ -48,3 +48,13 @@ func Test_Duration(t *testing.T) {
t.Assert(d.Nanoseconds(), 1000000000)
})
}
func Test_ConvertWithRefer(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.AssertEQ(gconv.ConvertWithRefer("1", 100), 1)
t.AssertEQ(gconv.ConvertWithRefer("1.01", 1.111), 1.01)
t.AssertEQ(gconv.ConvertWithRefer("1.01", "1.111"), "1.01")
t.AssertEQ(gconv.ConvertWithRefer("1.01", false), true)
t.AssertNE(gconv.ConvertWithRefer("1.01", false), false)
})
}

View File

@ -13,8 +13,7 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
type boolStruct struct {
}
type boolStruct struct{}
func Test_Bool(t *testing.T) {
gtest.C(t, func(t *gtest.T) {

View File

@ -9,6 +9,9 @@ package gutil
import (
"reflect"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/util/gconv"
)
@ -36,3 +39,63 @@ func StructToSlice(data interface{}) []interface{} {
}
return nil
}
// FillStructWithDefault fills attributes of pointed struct with tag value from `default/d` tag .
// The parameter `structPtr` should be either type of *struct/[]*struct.
func FillStructWithDefault(structPtr interface{}) error {
var (
reflectValue reflect.Value
)
if rv, ok := structPtr.(reflect.Value); ok {
reflectValue = rv
} else {
reflectValue = reflect.ValueOf(structPtr)
}
switch reflectValue.Kind() {
case reflect.Ptr:
// Nothing to do.
case reflect.Array, reflect.Slice:
if reflectValue.Elem().Kind() != reflect.Ptr {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid parameter "%s", the element of slice should be type of pointer of struct, but given "%s"`,
reflectValue.Type().String(), reflectValue.Elem().Type().String(),
)
}
default:
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid parameter "%s", should be type of pointer of struct`,
reflectValue.Type().String(),
)
}
if reflectValue.IsNil() {
return gerror.NewCode(
gcode.CodeInvalidParameter,
`the pointed struct object should not be nil`,
)
}
if !reflectValue.Elem().IsValid() {
return gerror.NewCode(
gcode.CodeInvalidParameter,
`the pointed struct object should be valid`,
)
}
fields, err := gstructs.Fields(gstructs.FieldsInput{
Pointer: reflectValue,
RecursiveOption: gstructs.RecursiveOptionNone,
})
if err != nil {
return err
}
for _, field := range fields {
if defaultValue := field.TagDefault(); defaultValue != "" {
if field.IsEmpty() {
field.Value.Set(reflect.ValueOf(
gconv.ConvertWithRefer(defaultValue, field.Value),
))
}
}
}
return nil
}

View File

@ -36,3 +36,20 @@ func Test_StructToSlice(t *testing.T) {
t.Assert(s, nil)
})
}
func Test_FillStructWithDefault(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type A struct {
V1 int `d:"1.01"`
V2 string `d:"1.01"`
V3 float32 `d:"1.01"`
}
a := A{}
err := gutil.FillStructWithDefault(&a)
t.AssertNil(err)
t.Assert(a.V1, `1`)
t.Assert(a.V2, `1.01`)
t.Assert(a.V3, `1.01`)
})
}