mirror of
https://gitee.com/johng/gf
synced 2026-06-09 02:57:43 +08:00
Compare commits
5 Commits
feat/gfdep
...
v2.10.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c8c7c8c71 | |||
| 73211707fb | |||
| 609f44c5fe | |||
| 0a82036da5 | |||
| b4053ed32e |
@ -1,7 +1,6 @@
|
||||
version: "2"
|
||||
run:
|
||||
concurrency: 4
|
||||
go: "1.25"
|
||||
modules-download-mode: readonly
|
||||
issues-exit-code: 2
|
||||
tests: false
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Function to detect OS and set sed parameters
|
||||
setup_sed() {
|
||||
# Function to run sed in-place with OS-specific options
|
||||
sed_inplace() {
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
SED_INPLACE="sed -i ''"
|
||||
# macOS - requires empty string after -i
|
||||
sed -i '' "$@"
|
||||
else
|
||||
# Linux/Windows Git Bash
|
||||
SED_INPLACE="sed -i"
|
||||
sed -i "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Initialize sed command
|
||||
setup_sed
|
||||
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Parameter exception, please execute in the format of $0 [directory] [version number]"
|
||||
echo "PS:$0 ./ v2.4.0"
|
||||
@ -43,10 +40,11 @@ fi
|
||||
|
||||
if [[ true ]]; then
|
||||
# Use sed to replace the version number in version.go
|
||||
$SED_INPLACE 's/VERSION = ".*"/VERSION = "'${newVersion}'"/' version.go
|
||||
sed_inplace 's/VERSION = ".*"/VERSION = "'${newVersion}'"/' version.go
|
||||
|
||||
# Use sed to replace the version number in README.MD
|
||||
$SED_INPLACE 's/version=[^"]*/version='${newVersion}'/' README.MD
|
||||
sed_inplace 's/version=[^"]*/version='${newVersion}'/' README.MD
|
||||
sed_inplace 's/version=[^"]*/version='${newVersion}'/' README.zh_CN.MD
|
||||
fi
|
||||
|
||||
if [ -f "go.work" ]; then
|
||||
@ -70,6 +68,8 @@ for file in `find ${workdir} -name go.mod`; do
|
||||
fi
|
||||
|
||||
cd $goModPath
|
||||
|
||||
# Add replace directive for local development.
|
||||
if [ $goModPath = "./cmd/gf" ]; then
|
||||
mv go.work go.work.version.bak
|
||||
go mod edit -replace github.com/gogf/gf/v2=../../
|
||||
@ -81,20 +81,20 @@ for file in `find ${workdir} -name go.mod`; do
|
||||
go mod edit -replace github.com/gogf/gf/contrib/drivers/sqlite/v2=../../contrib/drivers/sqlite
|
||||
fi
|
||||
# Remove indirect dependencies
|
||||
sed -i '/\/\/ indirect/d' go.mod
|
||||
sed_inplace '/\/\/ indirect/d' go.mod
|
||||
go mod tidy
|
||||
# Remove toolchain line if exists
|
||||
$SED_INPLACE '/^toolchain/d' go.mod
|
||||
sed_inplace '/^toolchain/d' go.mod
|
||||
|
||||
# Upgrading only GoFrame related libraries, sometimes even if a version number is specified,
|
||||
# Upgrading only GoFrame related libraries, sometimes even if a version number is specified,
|
||||
# it may not be possible to successfully upgrade. Please confirm before submitting the code
|
||||
go list -f "{{if and (not .Indirect) (not .Main)}}{{.Path}}@${newVersion}{{end}}" -m all | grep "^github.com/gogf/gf"
|
||||
go list -f "{{if and (not .Indirect) (not .Main)}}{{.Path}}@${newVersion}{{end}}" -m all | grep "^github.com/gogf/gf" | xargs -L1 go get -v
|
||||
go list -f "{{if and (not .Indirect) (not .Main)}}{{.Path}}@${newVersion}{{end}}" -m all | grep "^github.com/gogf/gf" | xargs -L1 go get -v
|
||||
# Remove indirect dependencies
|
||||
sed -i '/\/\/ indirect/d' go.mod
|
||||
sed_inplace '/\/\/ indirect/d' go.mod
|
||||
go mod tidy
|
||||
# Remove toolchain line if exists
|
||||
$SED_INPLACE '/^toolchain/d' go.mod
|
||||
sed_inplace '/^toolchain/d' go.mod
|
||||
if [ $goModPath = "./cmd/gf" ]; then
|
||||
go mod edit -dropreplace github.com/gogf/gf/v2
|
||||
go mod edit -dropreplace github.com/gogf/gf/contrib/drivers/clickhouse/v2
|
||||
|
||||
@ -45,7 +45,7 @@ go get -u github.com/gogf/gf/v2
|
||||
💖 [Thanks to all the contributors who made GoFrame possible](https://github.com/gogf/gf/graphs/contributors) 💖
|
||||
|
||||
<a href="https://github.com/gogf/gf/graphs/contributors">
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.9.8" alt="goframe contributors"/>
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.10.0" alt="goframe contributors"/>
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
@ -45,7 +45,7 @@ go get -u github.com/gogf/gf/v2
|
||||
💖 [感谢所有使 GoFrame 成为可能的贡献者](https://github.com/gogf/gf/graphs/contributors) 💖
|
||||
|
||||
<a href="https://github.com/gogf/gf/graphs/contributors">
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.9.5" alt="goframe contributors"/>
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.10.0" alt="goframe contributors"/>
|
||||
</a>
|
||||
|
||||
## 许可证
|
||||
|
||||
@ -3,13 +3,13 @@ module github.com/gogf/gf/cmd/gf/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.10.0
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.10.0
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.10.0
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.10.0
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f
|
||||
github.com/olekukonko/tablewriter v1.1.0
|
||||
github.com/schollz/progressbar/v3 v3.15.0
|
||||
|
||||
@ -46,20 +46,6 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.8 h1:L72OB2HPuZSHtJ2ipBzI+62rGGDRdwYjequ1v+zctpg=
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.8/go.mod h1:D0UySg70Bd264F5AScYmz1Hl8vjzlUJ7YvqBJc5OFbo=
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.8 h1:DT5zHfo9/VkbJ+TF7kUasvv4dbU5uctoj+JGbrzgdYE=
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.8/go.mod h1:cDd91Zd8LxFF+xxOflRRqw0WTTCpAJ0nf0KKRA+nvTE=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.8 h1:XZ4Ya/50xpjf81+4genr33iJXR2dxJmqYKxGyXlLRqA=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.8/go.mod h1:wtm2NJb/L3CbDOmyUc7TsOpWHTCMakg1QRG7B/oKrRs=
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.8 h1:ZrqABJsUnhNDz8VAem1XXONBTywl6r+GHQH05i+4W1g=
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.8/go.mod h1:YTFyeVk2Rgu/JMUhFxkjYzWaBc+yZ6wAvY54XVZoNko=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.8 h1:Dc227FD1uf9nNBPFEjMEgIoAJbAgeYeNrOrjviDgPzY=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.8/go.mod h1:o3EpB4Ti3+x/axzRMJg2k7TrLiWZiSTxP0v64LBkk5k=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.8 h1:LHEhzsBfIo8xHvOUuLDQW1q7Qix1vnBabH/iivCRghs=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.8/go.mod h1:SX6dRONaJGafzCoMIrn8CkRM4fIvtmJRt/aYclUHy3Q=
|
||||
github.com/gogf/gf/v2 v2.9.8 h1:El0HwksTzeRk0DQV4Lh7S9DbsIwKInhHSHGcH7qJumM=
|
||||
github.com/gogf/gf/v2 v2.9.8/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f h1:7xfXR/BhG3JDqO1s45n65Oyx9t4E/UqDOXep6jXdLCM=
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f/go.mod h1:HnYoio6S7VaFJdryKcD/r9HgX+4QzYfr00XiXUo/xz0=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
|
||||
@ -238,3 +238,48 @@ func Test_Gen_Service_PackagesFilter(t *testing.T) {
|
||||
t.Assert(files[0], dstFolder+filepath.FromSlash("/user.go"))
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/4242
|
||||
// Test that versioned imports and aliased imports are correctly preserved.
|
||||
// The issue is that imports like "github.com/minio/minio-go/v7" were being
|
||||
// incorrectly handled because the package name (minio) differs from
|
||||
// the directory name (minio-go).
|
||||
func Test_Issue4242(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
path = gfile.Temp(guid.S())
|
||||
dstFolder = path + filepath.FromSlash("/service")
|
||||
srvFolder = gtest.DataPath("issue", "4242", "logic")
|
||||
in = genservice.CGenServiceInput{
|
||||
SrcFolder: srvFolder,
|
||||
DstFolder: dstFolder,
|
||||
DstFileNameCase: "Snake",
|
||||
WatchFile: "",
|
||||
StPattern: "",
|
||||
Packages: nil,
|
||||
ImportPrefix: "",
|
||||
Clear: false,
|
||||
}
|
||||
)
|
||||
err := gutil.FillStructWithDefault(&in)
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gfile.Mkdir(path)
|
||||
t.AssertNil(err)
|
||||
defer gfile.Remove(path)
|
||||
|
||||
_, err = genservice.CGenService{}.Service(ctx, in)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Test versioned imports
|
||||
t.Assert(
|
||||
gfile.GetContents(dstFolder+filepath.FromSlash("/issue_4242.go")),
|
||||
gfile.GetContents(gtest.DataPath("issue", "4242", "service", "issue_4242.go")),
|
||||
)
|
||||
// Test aliased imports
|
||||
t.Assert(
|
||||
gfile.GetContents(dstFolder+filepath.FromSlash("/issue_4242_alias.go")),
|
||||
gfile.GetContents(gtest.DataPath("issue", "4242", "service", "issue_4242_alias.go")),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
@ -37,21 +36,14 @@ func (c CGenService) calculateImportedItems(
|
||||
}
|
||||
|
||||
for _, item := range pkgItems {
|
||||
alias := item.Alias
|
||||
|
||||
// If the alias is _, it means that the package is not generated.
|
||||
if alias == "_" {
|
||||
// Skip anonymous imports
|
||||
if item.Alias == "_" {
|
||||
mlog.Debugf(`ignore anonymous package: %s`, item.RawImport)
|
||||
continue
|
||||
}
|
||||
// If the alias is empty, it will use the package name as the alias.
|
||||
if alias == "" {
|
||||
alias = gfile.Basename(gstr.Trim(item.Path, `"`))
|
||||
}
|
||||
if !gstr.Contains(allFuncParamType.String(), alias) {
|
||||
mlog.Debugf(`ignore unused package: %s`, item.RawImport)
|
||||
continue
|
||||
}
|
||||
// Keep all imports, let gofmt clean up unused ones.
|
||||
// We cannot accurately infer package name from import path
|
||||
// (e.g., path "minio-go" but package name is "minio").
|
||||
srcImportedPackages.Add(item.RawImport)
|
||||
}
|
||||
return nil
|
||||
|
||||
34
cmd/gf/internal/cmd/testdata/issue/4242/logic/issue4242/issue4242.go
vendored
Normal file
34
cmd/gf/internal/cmd/testdata/issue/4242/logic/issue4242/issue4242.go
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
package issue4242
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/issue/4242/service"
|
||||
|
||||
"github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
service.RegisterIssue4242(New())
|
||||
}
|
||||
|
||||
type sIssue4242 struct {
|
||||
}
|
||||
|
||||
func New() *sIssue4242 {
|
||||
return &sIssue4242{}
|
||||
}
|
||||
|
||||
// GetDriver tests versioned import path is preserved.
|
||||
func (s *sIssue4242) GetDriver(ctx context.Context) (d mysql.Driver, err error) {
|
||||
return mysql.Driver{}, nil
|
||||
}
|
||||
|
||||
// GetRequest tests another versioned import.
|
||||
func (s *sIssue4242) GetRequest(ctx context.Context) (*ghttp.Request, error) {
|
||||
g.Log().Info(ctx, "getting request")
|
||||
return nil, nil
|
||||
}
|
||||
37
cmd/gf/internal/cmd/testdata/issue/4242/logic/issue4242alias/issue4242alias.go
vendored
Normal file
37
cmd/gf/internal/cmd/testdata/issue/4242/logic/issue4242alias/issue4242alias.go
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
package issue4242alias
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
// Anonymous import (should be skipped)
|
||||
_ "github.com/gogf/gf/v2/os/gres"
|
||||
|
||||
// Versioned import without alias
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/issue/4242/service"
|
||||
|
||||
// Explicit alias import
|
||||
mysqlDriver "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
service.RegisterIssue4242Alias(New())
|
||||
}
|
||||
|
||||
type sIssue4242Alias struct {
|
||||
}
|
||||
|
||||
func New() *sIssue4242Alias {
|
||||
return &sIssue4242Alias{}
|
||||
}
|
||||
|
||||
// GetDriver tests explicit alias import.
|
||||
func (s *sIssue4242Alias) GetDriver(ctx context.Context) (d mysqlDriver.Driver, err error) {
|
||||
return mysqlDriver.Driver{}, nil
|
||||
}
|
||||
|
||||
// GetRequest tests versioned import.
|
||||
func (s *sIssue4242Alias) GetRequest(ctx context.Context) (*ghttp.Request, error) {
|
||||
return nil, nil
|
||||
}
|
||||
10
cmd/gf/internal/cmd/testdata/issue/4242/logic/logic.go
vendored
Normal file
10
cmd/gf/internal/cmd/testdata/issue/4242/logic/logic.go
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
_ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/issue/4242/logic/issue4242"
|
||||
_ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/issue/4242/logic/issue4242alias"
|
||||
)
|
||||
37
cmd/gf/internal/cmd/testdata/issue/4242/service/issue_4242.go
vendored
Normal file
37
cmd/gf/internal/cmd/testdata/issue/4242/service/issue_4242.go
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// ================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// You can delete these comments if you wish manually maintain this interface file.
|
||||
// ================================================================================
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
type (
|
||||
IIssue4242 interface {
|
||||
// GetDriver tests versioned import path is preserved.
|
||||
GetDriver(ctx context.Context) (d mysql.Driver, err error)
|
||||
// GetRequest tests another versioned import.
|
||||
GetRequest(ctx context.Context) (*ghttp.Request, error)
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
localIssue4242 IIssue4242
|
||||
)
|
||||
|
||||
func Issue4242() IIssue4242 {
|
||||
if localIssue4242 == nil {
|
||||
panic("implement not found for interface IIssue4242, forgot register?")
|
||||
}
|
||||
return localIssue4242
|
||||
}
|
||||
|
||||
func RegisterIssue4242(i IIssue4242) {
|
||||
localIssue4242 = i
|
||||
}
|
||||
37
cmd/gf/internal/cmd/testdata/issue/4242/service/issue_4242_alias.go
vendored
Normal file
37
cmd/gf/internal/cmd/testdata/issue/4242/service/issue_4242_alias.go
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// ================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// You can delete these comments if you wish manually maintain this interface file.
|
||||
// ================================================================================
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
mysqlDriver "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
type (
|
||||
IIssue4242Alias interface {
|
||||
// GetDriver tests explicit alias import.
|
||||
GetDriver(ctx context.Context) (d mysqlDriver.Driver, err error)
|
||||
// GetRequest tests versioned import.
|
||||
GetRequest(ctx context.Context) (*ghttp.Request, error)
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
localIssue4242Alias IIssue4242Alias
|
||||
)
|
||||
|
||||
func Issue4242Alias() IIssue4242Alias {
|
||||
if localIssue4242Alias == nil {
|
||||
panic("implement not found for interface IIssue4242Alias, forgot register?")
|
||||
}
|
||||
return localIssue4242Alias
|
||||
}
|
||||
|
||||
func RegisterIssue4242Alias(i IIssue4242Alias) {
|
||||
localIssue4242Alias = i
|
||||
}
|
||||
@ -749,7 +749,9 @@ func (a *TArray[T]) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
// Note that do not use pointer as its receiver here.
|
||||
// DO NOT change this receiver to pointer type, as the TArray can be used as a var defined variable, like:
|
||||
// var a TArray[int]
|
||||
// Please refer to corresponding tests for more details.
|
||||
func (a TArray[T]) MarshalJSON() ([]byte, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
@ -46,7 +46,7 @@ func NewSortedTArray[T comparable](comparator func(a, b T) int, safe ...bool) *S
|
||||
return NewSortedTArraySize(0, comparator, safe...)
|
||||
}
|
||||
|
||||
// NewSortedTArraySize create and returns an sorted array with given size and cap.
|
||||
// NewSortedTArraySize create and returns a sorted array with given size and cap.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedTArraySize[T comparable](cap int, comparator func(a, b T) int, safe ...bool) *SortedTArray[T] {
|
||||
@ -718,7 +718,9 @@ func (a *SortedTArray[T]) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
// Note that do not use pointer as its receiver here.
|
||||
// DO NOT change this receiver to pointer type, as the TArray can be used as a var defined variable, like:
|
||||
// var a SortedTArray[int]
|
||||
// Please refer to corresponding tests for more details.
|
||||
func (a SortedTArray[T]) MarshalJSON() ([]byte, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
@ -22,8 +22,11 @@ type NilChecker[V any] func(V) bool
|
||||
|
||||
// KVMap wraps map type `map[K]V` and provides more map features.
|
||||
type KVMap[K comparable, V any] struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[K]V
|
||||
mu rwmutex.RWMutex
|
||||
data map[K]V
|
||||
|
||||
// nilChecker is the custom nil checker function.
|
||||
// It uses empty.IsNil if it's nil.
|
||||
nilChecker NilChecker[V]
|
||||
}
|
||||
|
||||
@ -58,15 +61,15 @@ func NewKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *KVMap[K, V]
|
||||
// The parameter `safe` is used to specify whether to use the map in concurrent-safety mode, which is false by default.
|
||||
func NewKVMapWithCheckerFrom[K comparable, V any](data map[K]V, checker NilChecker[V], safe ...bool) *KVMap[K, V] {
|
||||
m := NewKVMapFrom[K, V](data, safe...)
|
||||
m.RegisterNilChecker(checker)
|
||||
m.SetNilChecker(checker)
|
||||
return m
|
||||
}
|
||||
|
||||
// RegisterNilChecker registers a custom nil checker function for the map values.
|
||||
// SetNilChecker registers a custom nil checker function for the map values.
|
||||
// This function is used to determine if a value should be considered as nil.
|
||||
// The nil checker function takes a value of type V and returns a boolean indicating
|
||||
// whether the value should be treated as nil.
|
||||
func (m *KVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
func (m *KVMap[K, V]) SetNilChecker(nilChecker NilChecker[V]) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.nilChecker = nilChecker
|
||||
@ -74,12 +77,12 @@ func (m *KVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
|
||||
// isNil checks whether the given value is nil.
|
||||
// It first checks if a custom nil checker function is registered and uses it if available,
|
||||
// otherwise it performs a standard nil check using any(v) == nil.
|
||||
// otherwise it falls back to the default empty.IsNil function.
|
||||
func (m *KVMap[K, V]) isNil(v V) bool {
|
||||
if m.nilChecker != nil {
|
||||
return m.nilChecker(v)
|
||||
}
|
||||
return any(v) == nil
|
||||
return empty.IsNil(v)
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
@ -242,11 +245,12 @@ func (m *KVMap[K, V]) Pops(size int) map[K]V {
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
// doSetWithLockCheck sets value with given `value` if it does not exist,
|
||||
// and then returns this value and whether it exists.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
// It is a helper function for GetOrSet* functions.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the given `value` is nil.
|
||||
func (m *KVMap[K, V]) doSetWithLockCheck(key K, value V) (val V, ok bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
@ -274,6 +278,8 @@ func (m *KVMap[K, V]) GetOrSet(key K, value V) V {
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil.
|
||||
func (m *KVMap[K, V]) GetOrSetFunc(key K, f func() V) V {
|
||||
v, _ := m.doSetWithLockCheck(key, f())
|
||||
return v
|
||||
@ -285,6 +291,8 @@ func (m *KVMap[K, V]) GetOrSetFunc(key K, f func() V) V {
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil.
|
||||
func (m *KVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
@ -524,6 +532,9 @@ func (m *KVMap[K, V]) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
// DO NOT change this receiver to pointer type, as the KVMap can be used as a var defined variable, like:
|
||||
// var m gmap.KVMap[int, string]
|
||||
// Please refer to corresponding tests for more details.
|
||||
func (m KVMap[K, V]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ func NewListKVMap[K comparable, V any](safe ...bool) *ListKVMap[K, V] {
|
||||
// which is false by default.
|
||||
func NewListKVMapWithChecker[K comparable, V any](checker NilChecker[V], safe ...bool) *ListKVMap[K, V] {
|
||||
m := NewListKVMap[K, V](safe...)
|
||||
m.RegisterNilChecker(checker)
|
||||
m.SetNilChecker(checker)
|
||||
return m
|
||||
}
|
||||
|
||||
@ -81,11 +81,11 @@ func NewListKVMapWithCheckerFrom[K comparable, V any](data map[K]V, nilChecker N
|
||||
return m
|
||||
}
|
||||
|
||||
// RegisterNilChecker registers a custom nil checker function for the map values.
|
||||
// SetNilChecker registers a custom nil checker function for the map values.
|
||||
// This function is used to determine if a value should be considered as nil.
|
||||
// The nil checker function takes a value of type V and returns a boolean indicating
|
||||
// whether the value should be treated as nil.
|
||||
func (m *ListKVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
func (m *ListKVMap[K, V]) SetNilChecker(nilChecker NilChecker[V]) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.nilChecker = nilChecker
|
||||
@ -93,12 +93,12 @@ func (m *ListKVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
|
||||
// isNil checks whether the given value is nil.
|
||||
// It first checks if a custom nil checker function is registered and uses it if available,
|
||||
// otherwise it performs a standard nil check using any(v) == nil.
|
||||
// otherwise it falls back to the default empty.IsNil function.
|
||||
func (m *ListKVMap[K, V]) isNil(v V) bool {
|
||||
if m.nilChecker != nil {
|
||||
return m.nilChecker(v)
|
||||
}
|
||||
return any(v) == nil
|
||||
return empty.IsNil(v)
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
@ -402,6 +402,8 @@ func (m *ListKVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
//
|
||||
// Note that it does not add the value to the map if `value` is nil.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
@ -421,6 +423,8 @@ func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
@ -444,6 +448,8 @@ func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the map.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
@ -609,6 +615,9 @@ func (m *ListKVMap[K, V]) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
// DO NOT change this receiver to pointer type, as the ListKVMap can be used as a var defined variable, like:
|
||||
// var m gmap.ListKVMap[string]string
|
||||
// Please refer to corresponding tests for more details.
|
||||
func (m ListKVMap[K, V]) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
if m.data == nil {
|
||||
return []byte("{}"), nil
|
||||
|
||||
@ -774,6 +774,13 @@ func Test_KVMap_MarshalJSON(t *testing.T) {
|
||||
t.Assert(data["a"], 1)
|
||||
t.Assert(data["b"], 2)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var m gmap.KVMap[int, int]
|
||||
m.Set(1, 10)
|
||||
b, err := json.Marshal(m)
|
||||
t.AssertNil(err)
|
||||
t.Assert(string(b), `{"1":10}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_KVMap_UnmarshalJSON(t *testing.T) {
|
||||
@ -1647,9 +1654,10 @@ func Test_KVMap_TypedNil(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
t.Assert(m1.Size(), 10)
|
||||
t.Assert(m1.Size(), 5)
|
||||
|
||||
m2 := gmap.NewKVMap[int, *Student](true)
|
||||
m2.RegisterNilChecker(func(student *Student) bool {
|
||||
m2.SetNilChecker(func(student *Student) bool {
|
||||
return student == nil
|
||||
})
|
||||
for i := 0; i < 10; i++ {
|
||||
@ -1679,7 +1687,8 @@ func Test_NewKVMapWithChecker_TypedNil(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
t.Assert(m1.Size(), 10)
|
||||
t.Assert(m1.Size(), 5)
|
||||
|
||||
m2 := gmap.NewKVMapWithChecker[int, *Student](func(student *Student) bool {
|
||||
return student == nil
|
||||
}, true)
|
||||
|
||||
@ -183,77 +183,6 @@ func Test_ListKVMap_SetIfNotExistFuncLock_MultipleKeys(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_NilValue tests that nil values are handled correctly.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_NilValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *int](true)
|
||||
key := "nilKey"
|
||||
callCount := int32(0)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
goroutines := 50
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
m.GetOrSetFuncLock(key, func() *int {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should be called once
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
// Typed nil pointer (*int)(nil) is stored because any(value) != nil for typed nil
|
||||
// This is a Go language feature: typed nil is not the same as interface nil
|
||||
t.Assert(m.Contains(key), true)
|
||||
t.Assert(m.Get(key), (*int)(nil))
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLock_NilValue tests that nil values are handled correctly.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLock_NilValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *string](true)
|
||||
key := "nilKey"
|
||||
callCount := int32(0)
|
||||
successCount := int32(0)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
goroutines := 50
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
success := m.SetIfNotExistFuncLock(key, func() *string {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return nil
|
||||
})
|
||||
if success {
|
||||
atomic.AddInt32(&successCount, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should be called once
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
// Should report success once
|
||||
t.Assert(atomic.LoadInt32(&successCount), 1)
|
||||
// Typed nil pointer (*string)(nil) is stored because any(value) != nil for typed nil
|
||||
t.Assert(m.Contains(key), true)
|
||||
t.Assert(m.Get(key), (*string)(nil))
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_ExistingKey tests behavior when key already exists.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_ExistingKey(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
@ -1159,6 +1159,13 @@ func Test_ListKVMap_MarshalJSON_Error(t *testing.T) {
|
||||
t.AssertNil(err)
|
||||
t.Assert(string(b), `{"a":"1"}`)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var m gmap.ListKVMap[int, int]
|
||||
m.Set(1, 10)
|
||||
b, err := json.Marshal(m)
|
||||
t.AssertNil(err)
|
||||
t.Assert(string(b), `{"1":10}`)
|
||||
})
|
||||
}
|
||||
|
||||
// Test empty map operations
|
||||
@ -1358,9 +1365,10 @@ func Test_ListKVMap_TypedNil(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
t.Assert(m1.Size(), 10)
|
||||
t.Assert(m1.Size(), 5)
|
||||
|
||||
m2 := gmap.NewListKVMap[int, *Student](true)
|
||||
m2.RegisterNilChecker(func(student *Student) bool {
|
||||
m2.SetNilChecker(func(student *Student) bool {
|
||||
return student == nil
|
||||
})
|
||||
for i := 0; i < 10; i++ {
|
||||
@ -1390,7 +1398,8 @@ func Test_NewListKVMapWithChecker_TypedNil(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
t.Assert(m1.Size(), 10)
|
||||
t.Assert(m1.Size(), 5)
|
||||
|
||||
m2 := gmap.NewListKVMapWithChecker[int, *Student](func(student *Student) bool {
|
||||
return student == nil
|
||||
}, true)
|
||||
|
||||
@ -9,6 +9,7 @@ package gset
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
@ -39,7 +40,7 @@ func NewTSet[T comparable](safe ...bool) *TSet[T] {
|
||||
// The parameter `safe` is used to specify whether using set in concurrent-safety mode.
|
||||
func NewTSetWithChecker[T comparable](checker NilChecker[T], safe ...bool) *TSet[T] {
|
||||
s := NewTSet[T](safe...)
|
||||
s.RegisterNilChecker(checker)
|
||||
s.SetNilChecker(checker)
|
||||
return s
|
||||
}
|
||||
|
||||
@ -66,11 +67,11 @@ func NewTSetWithCheckerFrom[T comparable](items []T, checker NilChecker[T], safe
|
||||
return set
|
||||
}
|
||||
|
||||
// RegisterNilChecker registers a custom nil checker function for the set elements.
|
||||
// SetNilChecker registers a custom nil checker function for the set elements.
|
||||
// This function is used to determine if an element should be considered as nil.
|
||||
// The nil checker function takes an element of type T and returns a boolean indicating
|
||||
// whether the element should be treated as nil.
|
||||
func (set *TSet[T]) RegisterNilChecker(nilChecker NilChecker[T]) {
|
||||
func (set *TSet[T]) SetNilChecker(nilChecker NilChecker[T]) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
set.nilChecker = nilChecker
|
||||
@ -78,12 +79,12 @@ func (set *TSet[T]) RegisterNilChecker(nilChecker NilChecker[T]) {
|
||||
|
||||
// isNil checks whether the given value is nil.
|
||||
// It first checks if a custom nil checker function is registered and uses it if available,
|
||||
// otherwise it performs a standard nil check using any(v) == nil.
|
||||
// otherwise it falls back to the default empty.IsNil function.
|
||||
func (set *TSet[T]) isNil(v T) bool {
|
||||
if set.nilChecker != nil {
|
||||
return set.nilChecker(v)
|
||||
}
|
||||
return any(v) == nil
|
||||
return empty.IsNil(v)
|
||||
}
|
||||
|
||||
// Iterator iterates the set readonly with given callback function `f`,
|
||||
@ -109,7 +110,7 @@ func (set *TSet[T]) Add(items ...T) {
|
||||
}
|
||||
|
||||
// AddIfNotExist checks whether item exists in the set,
|
||||
// it adds the item to set and returns true if it does not exists in the set,
|
||||
// it adds the item to set and returns true if it does not exist in the set,
|
||||
// or else it does nothing and returns false.
|
||||
//
|
||||
// Note that, if `item` is nil, it does nothing and returns false.
|
||||
|
||||
@ -601,10 +601,10 @@ func Test_TSet_TypedNil(t *testing.T) {
|
||||
set := gset.NewTSet[*Student](true)
|
||||
var s *Student = nil
|
||||
exist := set.AddIfNotExist(s)
|
||||
t.Assert(exist, true)
|
||||
t.Assert(exist, false)
|
||||
|
||||
set2 := gset.NewTSet[*Student](true)
|
||||
set2.RegisterNilChecker(func(student *Student) bool {
|
||||
set2.SetNilChecker(func(student *Student) bool {
|
||||
return student == nil
|
||||
})
|
||||
exist2 := set2.AddIfNotExist(s)
|
||||
@ -621,7 +621,7 @@ func Test_NewTSetWithChecker_TypedNil(t *testing.T) {
|
||||
set := gset.NewTSet[*Student](true)
|
||||
var s *Student = nil
|
||||
exist := set.AddIfNotExist(s)
|
||||
t.Assert(exist, true)
|
||||
t.Assert(exist, false)
|
||||
|
||||
set2 := gset.NewTSetWithChecker[*Student](func(student *Student) bool {
|
||||
return student == nil
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/emirpasic/gods/v2/trees/avltree"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
@ -52,7 +53,7 @@ func NewAVLKVTree[K comparable, V any](comparator func(v1, v2 K) int, safe ...bo
|
||||
// The parameter `checker` is used to specify whether the given value is nil.
|
||||
func NewAVLKVTreeWithChecker[K comparable, V any](comparator func(v1, v2 K) int, checker NilChecker[V], safe ...bool) *AVLKVTree[K, V] {
|
||||
t := NewAVLKVTree[K, V](comparator, safe...)
|
||||
t.RegisterNilChecker(checker)
|
||||
t.SetNilChecker(checker)
|
||||
return t
|
||||
}
|
||||
|
||||
@ -78,11 +79,11 @@ func NewAVLKVTreeWithCheckerFrom[K comparable, V any](comparator func(v1, v2 K)
|
||||
return tree
|
||||
}
|
||||
|
||||
// RegisterNilChecker registers a custom nil checker function for the map values.
|
||||
// SetNilChecker registers a custom nil checker function for the map values.
|
||||
// This function is used to determine if a value should be considered as nil.
|
||||
// The nil checker function takes a value of type V and returns a boolean indicating
|
||||
// whether the value should be treated as nil.
|
||||
func (tree *AVLKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
func (tree *AVLKVTree[K, V]) SetNilChecker(nilChecker NilChecker[V]) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.nilChecker = nilChecker
|
||||
@ -90,12 +91,12 @@ func (tree *AVLKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
|
||||
// isNil checks whether the given value is nil.
|
||||
// It first checks if a custom nil checker function is registered and uses it if available,
|
||||
// otherwise it performs a standard nil check using any(v) == nil.
|
||||
func (tree *AVLKVTree[K, V]) isNil(value V) bool {
|
||||
// otherwise it falls back to the default empty.IsNil function.
|
||||
func (tree *AVLKVTree[K, V]) isNil(v V) bool {
|
||||
if tree.nilChecker != nil {
|
||||
return tree.nilChecker(value)
|
||||
return tree.nilChecker(v)
|
||||
}
|
||||
return any(value) == nil
|
||||
return empty.IsNil(v)
|
||||
}
|
||||
|
||||
// Clone clones and returns a new tree from current tree.
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/emirpasic/gods/v2/trees/btree"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
@ -51,7 +52,7 @@ func NewBKVTree[K comparable, V any](m int, comparator func(v1, v2 K) int, safe
|
||||
// The parameter `checker` is used to specify whether the given value is nil.
|
||||
func NewBKVTreeWithChecker[K comparable, V any](m int, comparator func(v1, v2 K) int, checker NilChecker[V], safe ...bool) *BKVTree[K, V] {
|
||||
t := NewBKVTree[K, V](m, comparator, safe...)
|
||||
t.RegisterNilChecker(checker)
|
||||
t.SetNilChecker(checker)
|
||||
return t
|
||||
}
|
||||
|
||||
@ -77,11 +78,11 @@ func NewBKVTreeWithCheckerFrom[K comparable, V any](m int, comparator func(v1, v
|
||||
return tree
|
||||
}
|
||||
|
||||
// RegisterNilChecker registers a custom nil checker function for the map values.
|
||||
// SetNilChecker registers a custom nil checker function for the map values.
|
||||
// This function is used to determine if a value should be considered as nil.
|
||||
// The nil checker function takes a value of type V and returns a boolean indicating
|
||||
// whether the value should be treated as nil.
|
||||
func (tree *BKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
func (tree *BKVTree[K, V]) SetNilChecker(nilChecker NilChecker[V]) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.nilChecker = nilChecker
|
||||
@ -89,12 +90,12 @@ func (tree *BKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
|
||||
// isNil checks whether the given value is nil.
|
||||
// It first checks if a custom nil checker function is registered and uses it if available,
|
||||
// otherwise it performs a standard nil check using any(v) == nil.
|
||||
func (tree *BKVTree[K, V]) isNil(value V) bool {
|
||||
// otherwise it falls back to the default empty.IsNil function.
|
||||
func (tree *BKVTree[K, V]) isNil(v V) bool {
|
||||
if tree.nilChecker != nil {
|
||||
return tree.nilChecker(value)
|
||||
return tree.nilChecker(v)
|
||||
}
|
||||
return any(value) == nil
|
||||
return empty.IsNil(v)
|
||||
}
|
||||
|
||||
// Clone clones and returns a new tree from current tree.
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/emirpasic/gods/v2/trees/redblacktree"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
@ -47,7 +48,7 @@ func NewRedBlackKVTree[K comparable, V any](comparator func(v1, v2 K) int, safe
|
||||
// The parameter `checker` is used to specify whether the given value is nil.
|
||||
func NewRedBlackKVTreeWithChecker[K comparable, V any](comparator func(v1, v2 K) int, checker NilChecker[V], safe ...bool) *RedBlackKVTree[K, V] {
|
||||
t := NewRedBlackKVTree[K, V](comparator, safe...)
|
||||
t.RegisterNilChecker(checker)
|
||||
t.SetNilChecker(checker)
|
||||
return t
|
||||
}
|
||||
|
||||
@ -96,11 +97,11 @@ func RedBlackKVTreeInitFrom[K comparable, V any](tree *RedBlackKVTree[K, V], com
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterNilChecker registers a custom nil checker function for the map values.
|
||||
// SetNilChecker registers a custom nil checker function for the map values.
|
||||
// This function is used to determine if a value should be considered as nil.
|
||||
// The nil checker function takes a value of type V and returns a boolean indicating
|
||||
// whether the value should be treated as nil.
|
||||
func (tree *RedBlackKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
func (tree *RedBlackKVTree[K, V]) SetNilChecker(nilChecker NilChecker[V]) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.nilChecker = nilChecker
|
||||
@ -108,12 +109,12 @@ func (tree *RedBlackKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
|
||||
// isNil checks whether the given value is nil.
|
||||
// It first checks if a custom nil checker function is registered and uses it if available,
|
||||
// otherwise it performs a standard nil check using any(v) == nil.
|
||||
func (tree *RedBlackKVTree[K, V]) isNil(value V) bool {
|
||||
// otherwise it falls back to the default empty.IsNil function.
|
||||
func (tree *RedBlackKVTree[K, V]) isNil(v V) bool {
|
||||
if tree.nilChecker != nil {
|
||||
return tree.nilChecker(value)
|
||||
return tree.nilChecker(v)
|
||||
}
|
||||
return any(value) == nil
|
||||
return empty.IsNil(v)
|
||||
}
|
||||
|
||||
// SetComparator sets/changes the comparator for sorting.
|
||||
|
||||
@ -29,9 +29,10 @@ func Test_KVAVLTree_TypedNil(t *testing.T) {
|
||||
avlTree.Set(i, s)
|
||||
}
|
||||
}
|
||||
t.Assert(avlTree.Size(), 10)
|
||||
t.Assert(avlTree.Size(), 5)
|
||||
|
||||
avlTree2 := gtree.NewAVLKVTree[int, *Student](gutil.ComparatorTStr[int], true)
|
||||
avlTree2.RegisterNilChecker(func(student *Student) bool {
|
||||
avlTree2.SetNilChecker(func(student *Student) bool {
|
||||
return student == nil
|
||||
})
|
||||
for i := 0; i < 10; i++ {
|
||||
@ -62,9 +63,10 @@ func Test_KVBTree_TypedNil(t *testing.T) {
|
||||
btree.Set(i, s)
|
||||
}
|
||||
}
|
||||
t.Assert(btree.Size(), 10)
|
||||
t.Assert(btree.Size(), 5)
|
||||
|
||||
btree2 := gtree.NewBKVTree[int, *Student](100, gutil.ComparatorTStr[int], true)
|
||||
btree2.RegisterNilChecker(func(student *Student) bool {
|
||||
btree2.SetNilChecker(func(student *Student) bool {
|
||||
return student == nil
|
||||
})
|
||||
for i := 0; i < 10; i++ {
|
||||
@ -95,10 +97,10 @@ func Test_KVRedBlackTree_TypedNil(t *testing.T) {
|
||||
redBlackTree.Set(i, s)
|
||||
}
|
||||
}
|
||||
t.Assert(redBlackTree.Size(), 10)
|
||||
redBlackTree2 := gtree.NewRedBlackKVTree[int, *Student](gutil.ComparatorTStr[int], true)
|
||||
t.Assert(redBlackTree.Size(), 5)
|
||||
|
||||
redBlackTree2.RegisterNilChecker(func(student *Student) bool {
|
||||
redBlackTree2 := gtree.NewRedBlackKVTree[int, *Student](gutil.ComparatorTStr[int], true)
|
||||
redBlackTree2.SetNilChecker(func(student *Student) bool {
|
||||
return student == nil
|
||||
})
|
||||
for i := 0; i < 10; i++ {
|
||||
@ -128,7 +130,8 @@ func Test_NewKVAVLTreeWithChecker_TypedNil(t *testing.T) {
|
||||
avlTree.Set(i, s)
|
||||
}
|
||||
}
|
||||
t.Assert(avlTree.Size(), 10)
|
||||
t.Assert(avlTree.Size(), 5)
|
||||
|
||||
avlTree2 := gtree.NewAVLKVTreeWithChecker[int, *Student](gutil.ComparatorTStr[int], func(student *Student) bool {
|
||||
return student == nil
|
||||
}, true)
|
||||
@ -160,7 +163,8 @@ func Test_NewKVBTreeWithChecker_TypedNil(t *testing.T) {
|
||||
btree.Set(i, s)
|
||||
}
|
||||
}
|
||||
t.Assert(btree.Size(), 10)
|
||||
t.Assert(btree.Size(), 5)
|
||||
|
||||
btree2 := gtree.NewBKVTreeWithChecker[int, *Student](100, gutil.ComparatorTStr[int], func(student *Student) bool {
|
||||
return student == nil
|
||||
}, true)
|
||||
@ -192,7 +196,8 @@ func Test_NewRedBlackKVTreeWithChecker_TypedNil(t *testing.T) {
|
||||
redBlackTree.Set(i, s)
|
||||
}
|
||||
}
|
||||
t.Assert(redBlackTree.Size(), 10)
|
||||
t.Assert(redBlackTree.Size(), 5)
|
||||
|
||||
redBlackTree2 := gtree.NewRedBlackKVTreeWithChecker[int, *Student](gutil.ComparatorTStr[int], func(student *Student) bool {
|
||||
return student == nil
|
||||
}, true)
|
||||
|
||||
@ -179,7 +179,7 @@ func (c *Client) updateLocalValue(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
// AddWatcher adds a watcher for the specified configuration file.
|
||||
func (c *Client) AddWatcher(name string, f func(ctx context.Context)) {
|
||||
func (c *Client) AddWatcher(name string, f gcfg.WatcherFunc) {
|
||||
c.watchers.Add(name, f)
|
||||
}
|
||||
|
||||
@ -193,6 +193,11 @@ func (c *Client) GetWatcherNames() []string {
|
||||
return c.watchers.GetNames()
|
||||
}
|
||||
|
||||
// IsWatching checks whether the watcher with the specified name is registered.
|
||||
func (c *Client) IsWatching(name string) bool {
|
||||
return c.watchers.IsWatching(name)
|
||||
}
|
||||
|
||||
// notifyWatchers notifies all watchers.
|
||||
func (c *Client) notifyWatchers(ctx context.Context) {
|
||||
c.watchers.Notify(ctx)
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/apolloconfig/agollo/v4 v4.3.1
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -207,7 +207,7 @@ func (c *Client) startAsynchronousWatch(plan *watch.Plan) {
|
||||
}
|
||||
|
||||
// AddWatcher adds a watcher for the specified configuration file.
|
||||
func (c *Client) AddWatcher(name string, f func(ctx context.Context)) {
|
||||
func (c *Client) AddWatcher(name string, f gcfg.WatcherFunc) {
|
||||
c.watchers.Add(name, f)
|
||||
}
|
||||
|
||||
@ -221,6 +221,11 @@ func (c *Client) GetWatcherNames() []string {
|
||||
return c.watchers.GetNames()
|
||||
}
|
||||
|
||||
// IsWatching checks whether the watcher with the specified name is registered.
|
||||
func (c *Client) IsWatching(name string) bool {
|
||||
return c.watchers.IsWatching(name)
|
||||
}
|
||||
|
||||
// notifyWatchers notifies all watchers.
|
||||
func (c *Client) notifyWatchers(ctx context.Context) {
|
||||
c.watchers.Notify(ctx)
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/consul/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/hashicorp/consul/api v1.24.0
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
)
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/kubecm/v2
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
k8s.io/api v0.33.4
|
||||
k8s.io/apimachinery v0.33.4
|
||||
k8s.io/client-go v0.33.4
|
||||
|
||||
@ -199,7 +199,7 @@ func (c *Client) startAsynchronousWatch(ctx context.Context, namespace string, w
|
||||
}
|
||||
|
||||
// AddWatcher adds a watcher for the specified configuration file.
|
||||
func (c *Client) AddWatcher(name string, f func(ctx context.Context)) {
|
||||
func (c *Client) AddWatcher(name string, f gcfg.WatcherFunc) {
|
||||
c.watchers.Add(name, f)
|
||||
}
|
||||
|
||||
@ -213,6 +213,11 @@ func (c *Client) GetWatcherNames() []string {
|
||||
return c.watchers.GetNames()
|
||||
}
|
||||
|
||||
// IsWatching checks whether the watcher with the specified name is registered.
|
||||
func (c *Client) IsWatching(name string) bool {
|
||||
return c.watchers.IsWatching(name)
|
||||
}
|
||||
|
||||
// notifyWatchers notifies all watchers.
|
||||
func (c *Client) notifyWatchers(ctx context.Context) {
|
||||
c.watchers.Notify(ctx)
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/nacos/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.3.3
|
||||
)
|
||||
|
||||
|
||||
@ -152,7 +152,7 @@ func (c *Client) addWatcher() error {
|
||||
}
|
||||
|
||||
// AddWatcher adds a watcher for the specified configuration file.
|
||||
func (c *Client) AddWatcher(name string, f func(ctx context.Context)) {
|
||||
func (c *Client) AddWatcher(name string, f gcfg.WatcherFunc) {
|
||||
c.watchers.Add(name, f)
|
||||
}
|
||||
|
||||
@ -166,6 +166,11 @@ func (c *Client) GetWatcherNames() []string {
|
||||
return c.watchers.GetNames()
|
||||
}
|
||||
|
||||
// IsWatching checks whether the watcher with the specified name is registered.
|
||||
func (c *Client) IsWatching(name string) bool {
|
||||
return c.watchers.IsWatching(name)
|
||||
}
|
||||
|
||||
// notifyWatchers notifies all watchers.
|
||||
func (c *Client) notifyWatchers(ctx context.Context) {
|
||||
c.watchers.Notify(ctx)
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/polaris/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/polarismesh/polaris-go v1.6.1
|
||||
)
|
||||
|
||||
|
||||
@ -187,7 +187,7 @@ func (c *Client) startAsynchronousWatch(ctx context.Context, changeChan <-chan m
|
||||
}
|
||||
|
||||
// AddWatcher adds a watcher for the specified configuration file.
|
||||
func (c *Client) AddWatcher(name string, f func(ctx context.Context)) {
|
||||
func (c *Client) AddWatcher(name string, f gcfg.WatcherFunc) {
|
||||
c.watchers.Add(name, f)
|
||||
}
|
||||
|
||||
@ -201,6 +201,11 @@ func (c *Client) GetWatcherNames() []string {
|
||||
return c.watchers.GetNames()
|
||||
}
|
||||
|
||||
// IsWatching checks whether the watcher with the specified name is registered.
|
||||
func (c *Client) IsWatching(name string) bool {
|
||||
return c.watchers.IsWatching(name)
|
||||
}
|
||||
|
||||
// notifyWatchers notifies all watchers.
|
||||
func (c *Client) notifyWatchers(ctx context.Context) {
|
||||
c.watchers.Notify(ctx)
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.15
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
)
|
||||
|
||||
@ -108,7 +108,7 @@ func (d *Driver) doMergeInsert(
|
||||
one = list[0]
|
||||
oneLen = len(one)
|
||||
charL, charR = d.GetChars()
|
||||
conflictKeySet = gset.New(false)
|
||||
conflictKeySet = gset.NewStrSet(false)
|
||||
|
||||
// queryHolders: Handle data with Holder that need to be merged
|
||||
// queryValues: Handle data that need to be merged
|
||||
|
||||
@ -6,7 +6,7 @@ replace github.com/gogf/gf/v2 => ../../../
|
||||
|
||||
require (
|
||||
gitee.com/chunanyong/dm v1.8.12
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -307,7 +307,7 @@ func (d *Driver) doMergeInsert(
|
||||
one = list[0]
|
||||
oneLen = len(one)
|
||||
charL, charR = d.GetChars()
|
||||
conflictKeySet = gset.New(false)
|
||||
conflictKeySet = gset.NewStrSet(false)
|
||||
|
||||
// queryHolders: Handle data with Holder that need to be merged
|
||||
// queryValues: Handle data that need to be merged
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
gitee.com/opengauss/openGauss-connector-go-pq v1.0.7
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
)
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/drivers/mariadb/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/mssql/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/microsoft/go-mssqldb v1.7.1
|
||||
)
|
||||
|
||||
|
||||
@ -102,7 +102,7 @@ func (d *Driver) doMergeInsert(
|
||||
one = list[0]
|
||||
oneLen = len(one)
|
||||
charL, charR = d.GetChars()
|
||||
conflictKeySet = gset.New(false)
|
||||
conflictKeySet = gset.NewStrSet(false)
|
||||
|
||||
// queryHolders: Handle data with Holder that need to be merged
|
||||
// queryValues: Handle data that need to be merged
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/drivers/oceanbase/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/oracle/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/sijms/go-ora/v2 v2.7.10
|
||||
)
|
||||
|
||||
|
||||
@ -181,7 +181,7 @@ func (d *Driver) doMergeInsert(
|
||||
one = list[0]
|
||||
oneLen = len(one)
|
||||
charL, charR = d.GetChars()
|
||||
conflictKeySet = gset.New(false)
|
||||
conflictKeySet = gset.NewStrSet(false)
|
||||
|
||||
// queryHolders: Handle data with Holder that need to be upsert
|
||||
// queryValues: Handle data that need to be upsert
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/pgsql/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/lib/pq v1.10.9
|
||||
)
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/glebarez/go-sqlite v1.21.2
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
)
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/drivers/tidb/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/metric/otelmetric/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/nosql/redis/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/redis/go-redis/v9 v9.12.1
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
go.opentelemetry.io/otel/trace v1.38.0
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/consul/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/hashicorp/consul/api v1.26.1
|
||||
)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/etcd/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
go.etcd.io/etcd/client/v3 v3.5.17
|
||||
google.golang.org/grpc v1.59.0
|
||||
)
|
||||
|
||||
@ -2,7 +2,7 @@ module github.com/gogf/gf/contrib/registry/file/v2
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require github.com/gogf/gf/v2 v2.9.8
|
||||
require github.com/gogf/gf/v2 v2.10.0
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
|
||||
@ -1,24 +1,25 @@
|
||||
# GoFrame Nacos Registry
|
||||
|
||||
|
||||
Use `nacos` as service registration and discovery management.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
go get -u -v github.com/gogf/gf/contrib/registry/nacos/v2
|
||||
```
|
||||
|
||||
suggested using `go.mod`:
|
||||
|
||||
```
|
||||
require github.com/gogf/gf/contrib/registry/nacos/v2 latest
|
||||
```
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
### Reference example
|
||||
|
||||
[server](../../../example/registry/nacos/http/server/main.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@ -44,6 +45,7 @@ func main() {
|
||||
```
|
||||
|
||||
[client](../../../example/registry/nacos/http/client/main.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@ -78,4 +80,3 @@ func main() {
|
||||
## License
|
||||
|
||||
`GoFrame Nacos` is licensed under the [MIT License](../../../LICENSE), 100% free and open-source, forever.
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/registry/nacos/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.3.3
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.3.5
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -267,8 +267,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.3.3 h1:lvkBZcYkKENLVR1ubO+vGxTP2L4VtVSArLvYZKuu4Pk=
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.3.3/go.mod h1:ygUBdt7eGeYBt6Lz2HO3wx7crKXk25Mp80568emGMWU=
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.3.5 h1:Hux7C4N4rWhwBF5Zm4yyYskrs9VTgrRTA8DZjoEhQTs=
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.3.5/go.mod h1:ygUBdt7eGeYBt6Lz2HO3wx7crKXk25Mp80568emGMWU=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
|
||||
@ -82,7 +82,7 @@ func New(address string, opts ...constant.ClientOption) (reg *Registry) {
|
||||
return
|
||||
}
|
||||
|
||||
// New creates and returns registry with Config.
|
||||
// NewWithConfig creates and returns registry with Config.
|
||||
func NewWithConfig(ctx context.Context, config Config) (reg *Registry, err error) {
|
||||
// Data validation.
|
||||
err = g.Validator().Data(config).Run(ctx)
|
||||
|
||||
@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
// Search searches and returns services with specified condition.
|
||||
func (reg *Registry) Search(ctx context.Context, in gsvc.SearchInput) (result []gsvc.Service, err error) {
|
||||
func (reg *Registry) Search(_ context.Context, in gsvc.SearchInput) (result []gsvc.Service, err error) {
|
||||
if in.Prefix == "" && in.Name != "" {
|
||||
in.Prefix = gsvc.NewServiceWithName(in.Name).GetPrefix()
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ import (
|
||||
|
||||
// Register registers `service` to Registry.
|
||||
// Note that it returns a new Service if it changes the input Service with custom one.
|
||||
func (reg *Registry) Register(ctx context.Context, service gsvc.Service) (registered gsvc.Service, err error) {
|
||||
func (reg *Registry) Register(_ context.Context, service gsvc.Service) (registered gsvc.Service, err error) {
|
||||
metadata := map[string]string{}
|
||||
endpoints := service.GetEndpoints()
|
||||
|
||||
@ -67,7 +67,7 @@ func (reg *Registry) Register(ctx context.Context, service gsvc.Service) (regist
|
||||
}
|
||||
|
||||
// Deregister off-lines and removes `service` from the Registry.
|
||||
func (reg *Registry) Deregister(ctx context.Context, service gsvc.Service) (err error) {
|
||||
func (reg *Registry) Deregister(_ context.Context, service gsvc.Service) (err error) {
|
||||
c := reg.client
|
||||
|
||||
for _, endpoint := range service.GetEndpoints() {
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/polaris/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/polarismesh/polaris-go v1.6.1
|
||||
)
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/go-zookeeper/zk v1.0.3
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
golang.org/x/sync v0.16.0
|
||||
)
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/rpc/grpcx/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.10.0
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
go.opentelemetry.io/otel/trace v1.38.0
|
||||
google.golang.org/grpc v1.64.1
|
||||
|
||||
@ -2,7 +2,7 @@ module github.com/gogf/gf/contrib/sdk/httpclient/v2
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require github.com/gogf/gf/v2 v2.9.8
|
||||
require github.com/gogf/gf/v2 v2.10.0
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/otlpgrpc/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/otlphttp/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
|
||||
|
||||
@ -513,18 +513,18 @@ type StatsItem interface {
|
||||
|
||||
// Core is the base struct for database management.
|
||||
type Core struct {
|
||||
db DB // DB interface object.
|
||||
ctx context.Context // Context for chaining operation only. Do not set a default value in Core initialization.
|
||||
group string // Configuration group name.
|
||||
schema string // Custom schema for this object.
|
||||
debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime.
|
||||
cache *gcache.Cache // Cache manager, SQL result cache only.
|
||||
links *gmap.Map // links caches all created links by node.
|
||||
logger glog.ILogger // Logger for logging functionality.
|
||||
config *ConfigNode // Current config node.
|
||||
localTypeMap *gmap.StrAnyMap // Local type map for database field type conversion.
|
||||
dynamicConfig dynamicConfig // Dynamic configurations, which can be changed in runtime.
|
||||
innerMemCache *gcache.Cache // Internal memory cache for storing temporary data.
|
||||
db DB // DB interface object.
|
||||
ctx context.Context // Context for chaining operation only. Do not set a default value in Core initialization.
|
||||
group string // Configuration group name.
|
||||
schema string // Custom schema for this object.
|
||||
debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime.
|
||||
cache *gcache.Cache // Cache manager, SQL result cache only.
|
||||
links *gmap.KVMap[ConfigNode, *sql.DB] // links caches all created links by node.
|
||||
logger glog.ILogger // Logger for logging functionality.
|
||||
config *ConfigNode // Current config node.
|
||||
localTypeMap *gmap.StrAnyMap // Local type map for database field type conversion.
|
||||
dynamicConfig dynamicConfig // Dynamic configurations, which can be changed in runtime.
|
||||
innerMemCache *gcache.Cache // Internal memory cache for storing temporary data.
|
||||
}
|
||||
|
||||
type dynamicConfig struct {
|
||||
@ -944,6 +944,9 @@ func NewByGroup(group ...string) (db DB, err error) {
|
||||
)
|
||||
}
|
||||
|
||||
// linksChecker is the checker function for links map.
|
||||
var linksChecker = func(v *sql.DB) bool { return v == nil }
|
||||
|
||||
// newDBByConfigNode creates and returns an ORM object with given configuration node and group name.
|
||||
//
|
||||
// Very Note:
|
||||
@ -960,7 +963,7 @@ func newDBByConfigNode(node *ConfigNode, group string) (db DB, err error) {
|
||||
group: group,
|
||||
debug: gtype.NewBool(),
|
||||
cache: gcache.New(),
|
||||
links: gmap.New(true),
|
||||
links: gmap.NewKVMapWithChecker[ConfigNode, *sql.DB](linksChecker, true),
|
||||
logger: glog.New(),
|
||||
config: node,
|
||||
localTypeMap: gmap.NewStrAnyMap(true),
|
||||
@ -1127,7 +1130,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
|
||||
// Cache the underlying connection pool object by node.
|
||||
var (
|
||||
instanceCacheFunc = func() any {
|
||||
instanceCacheFunc = func() *sql.DB {
|
||||
if sqlDb, err = c.db.Open(node); err != nil {
|
||||
return nil
|
||||
}
|
||||
@ -1159,7 +1162,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
)
|
||||
if instanceValue != nil && sqlDb == nil {
|
||||
// It reads from instance map.
|
||||
sqlDb = instanceValue.(*sql.DB)
|
||||
sqlDb = instanceValue
|
||||
}
|
||||
if node.Debug {
|
||||
c.db.SetDebug(node.Debug)
|
||||
|
||||
@ -113,19 +113,17 @@ func (c *Core) Close(ctx context.Context) (err error) {
|
||||
if err = c.cache.Close(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
c.links.LockFunc(func(m map[any]any) {
|
||||
c.links.LockFunc(func(m map[ConfigNode]*sql.DB) {
|
||||
for k, v := range m {
|
||||
if db, ok := v.(*sql.DB); ok {
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
err = gerror.WrapCode(gcode.CodeDbOperationError, err, `db.Close failed`)
|
||||
}
|
||||
intlog.Printf(ctx, `close link: %s, err: %v`, k, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
delete(m, k)
|
||||
err = v.Close()
|
||||
if err != nil {
|
||||
err = gerror.WrapCode(gcode.CodeDbOperationError, err, `db.Close failed`)
|
||||
}
|
||||
intlog.Printf(ctx, `close link: %s, err: %v`, gconv.String(k), err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
delete(m, k)
|
||||
}
|
||||
})
|
||||
return
|
||||
|
||||
@ -30,14 +30,14 @@ func (item *localStatsItem) Stats() sql.DBStats {
|
||||
// Stats retrieves and returns the pool stat for all nodes that have been established.
|
||||
func (c *Core) Stats(ctx context.Context) []StatsItem {
|
||||
var items = make([]StatsItem, 0)
|
||||
c.links.Iterator(func(k, v any) bool {
|
||||
var (
|
||||
node = k.(ConfigNode)
|
||||
sqlDB = v.(*sql.DB)
|
||||
)
|
||||
c.links.Iterator(func(k ConfigNode, v *sql.DB) bool {
|
||||
// Create a local copy of k to avoid loop variable address re-use issue
|
||||
// In Go, loop variables are re-used with the same memory address across iterations,
|
||||
// directly using &k would cause all localStatsItem instances to share the same address
|
||||
node := k
|
||||
items = append(items, &localStatsItem{
|
||||
node: &node,
|
||||
stats: sqlDB.Stats(),
|
||||
stats: v.Stats(),
|
||||
})
|
||||
return true
|
||||
})
|
||||
|
||||
@ -224,7 +224,6 @@ func loadContentWithOptions(data []byte, options Options) (*Json, error) {
|
||||
return NewWithOptions(decodedData, options), nil
|
||||
|
||||
default:
|
||||
|
||||
}
|
||||
// ignore some duplicated types, like js and yml,
|
||||
// which are not necessary shown in error message.
|
||||
|
||||
@ -29,12 +29,17 @@ type Adapter interface {
|
||||
Data(ctx context.Context) (data map[string]any, err error)
|
||||
}
|
||||
|
||||
// WatcherFunc is the callback function type for configuration watchers.
|
||||
type WatcherFunc = func(context.Context)
|
||||
|
||||
// WatcherAdapter is the interface for configuration watcher.
|
||||
type WatcherAdapter interface {
|
||||
// AddWatcher adds a watcher function for specified `pattern` and `resource`.
|
||||
AddWatcher(name string, fn func(ctx context.Context))
|
||||
AddWatcher(name string, fn WatcherFunc)
|
||||
// RemoveWatcher removes the watcher function for specified `pattern` and `resource`.
|
||||
RemoveWatcher(name string)
|
||||
// GetWatcherNames returns all watcher names.
|
||||
GetWatcherNames() []string
|
||||
// IsWatching checks and returns whether the specified `pattern` is watching.
|
||||
IsWatching(name string) bool
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ func (a *AdapterContent) Data(ctx context.Context) (data map[string]any, err err
|
||||
}
|
||||
|
||||
// AddWatcher adds a watcher for the specified configuration file.
|
||||
func (a *AdapterContent) AddWatcher(name string, fn func(ctx context.Context)) {
|
||||
func (a *AdapterContent) AddWatcher(name string, fn WatcherFunc) {
|
||||
a.watchers.Add(name, fn)
|
||||
}
|
||||
|
||||
@ -100,6 +100,11 @@ func (a *AdapterContent) GetWatcherNames() []string {
|
||||
return a.watchers.GetNames()
|
||||
}
|
||||
|
||||
// IsWatching checks and returns whether the specified `name` is watching.
|
||||
func (a *AdapterContent) IsWatching(name string) bool {
|
||||
return a.watchers.IsWatching(name)
|
||||
}
|
||||
|
||||
// notifyWatchers notifies all watchers.
|
||||
func (a *AdapterContent) notifyWatchers(ctx context.Context) {
|
||||
a.watchers.Notify(ctx)
|
||||
|
||||
@ -32,11 +32,11 @@ var (
|
||||
|
||||
// AdapterFile implements interface Adapter using file.
|
||||
type AdapterFile struct {
|
||||
defaultFileNameOrPath *gtype.String // Default configuration file name or file path.
|
||||
searchPaths *garray.StrArray // Searching the path array.
|
||||
jsonMap *gmap.StrAnyMap // The pared JSON objects for configuration files.
|
||||
violenceCheck bool // Whether it does violence check in value index searching. It affects the performance when set true(false in default).
|
||||
watchers *WatcherRegistry // Watchers for watching file changes.
|
||||
defaultFileNameOrPath *gtype.String // Default configuration file name or file path.
|
||||
searchPaths *garray.StrArray // Searching the path array.
|
||||
jsonMap *gmap.KVMap[string, *gjson.Json] // The parsed JSON objects for configuration files.
|
||||
violenceCheck bool // Whether it does violence check in value index searching. It affects the performance when set true(false in default).
|
||||
watchers *WatcherRegistry // Watchers for watching file changes.
|
||||
}
|
||||
|
||||
const (
|
||||
@ -58,6 +58,9 @@ var (
|
||||
|
||||
// Prefix array for trying searching in the local system.
|
||||
localSystemTryFolders = []string{"", "config/", "manifest/config"}
|
||||
|
||||
// jsonMapChecker is the checker for JSON map.
|
||||
jsonMapChecker = func(v *gjson.Json) bool { return v == nil }
|
||||
)
|
||||
|
||||
// NewAdapterFile returns a new configuration management object.
|
||||
@ -78,7 +81,7 @@ func NewAdapterFile(fileNameOrPath ...string) (*AdapterFile, error) {
|
||||
config := &AdapterFile{
|
||||
defaultFileNameOrPath: gtype.NewString(usedFileNameOrPath),
|
||||
searchPaths: garray.NewStrArray(true),
|
||||
jsonMap: gmap.NewStrAnyMap(true),
|
||||
jsonMap: gmap.NewKVMapWithChecker[string, *gjson.Json](jsonMapChecker, true),
|
||||
watchers: NewWatcherRegistry(),
|
||||
}
|
||||
// Customized dir path from env/cmd.
|
||||
@ -257,7 +260,7 @@ func (a *AdapterFile) getJson(fileNameOrPath ...string) (configJson *gjson.Json,
|
||||
usedFileNameOrPath = fileNameOrPath[0]
|
||||
}
|
||||
// It uses JSON map to cache specified configuration file content.
|
||||
result := a.jsonMap.GetOrSetFuncLock(usedFileNameOrPath, func() any {
|
||||
result := a.jsonMap.GetOrSetFuncLock(usedFileNameOrPath, func() *gjson.Json {
|
||||
var (
|
||||
content string
|
||||
filePath string
|
||||
@ -326,13 +329,13 @@ func (a *AdapterFile) getJson(fileNameOrPath ...string) (configJson *gjson.Json,
|
||||
return configJson
|
||||
})
|
||||
if result != nil {
|
||||
return result.(*gjson.Json), err
|
||||
return result, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddWatcher adds a watcher for the specified configuration file.
|
||||
func (a *AdapterFile) AddWatcher(name string, fn func(ctx context.Context)) {
|
||||
func (a *AdapterFile) AddWatcher(name string, fn WatcherFunc) {
|
||||
a.watchers.Add(name, fn)
|
||||
}
|
||||
|
||||
@ -346,6 +349,11 @@ func (a *AdapterFile) GetWatcherNames() []string {
|
||||
return a.watchers.GetNames()
|
||||
}
|
||||
|
||||
// IsWatching checks and returns whether the specified `name` is watching.
|
||||
func (a *AdapterFile) IsWatching(name string) bool {
|
||||
return a.watchers.IsWatching(name)
|
||||
}
|
||||
|
||||
// notifyWatchers notifies all watchers.
|
||||
func (a *AdapterFile) notifyWatchers(ctx context.Context) {
|
||||
a.watchers.Notify(ctx)
|
||||
|
||||
253
os/gcfg/gcfg_loader.go
Normal file
253
os/gcfg/gcfg_loader.go
Normal file
@ -0,0 +1,253 @@
|
||||
// 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 gcfg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
)
|
||||
|
||||
// Loader is a generic configuration manager that provides
|
||||
// configuration loading, watching and management similar to Spring Boot's @ConfigurationProperties
|
||||
type Loader[T any] struct {
|
||||
config *Config // The configuration instance to watch
|
||||
propertyKey string // The property key pattern to watch
|
||||
targetStruct *T // The target struct pointer to bind configuration to
|
||||
mutex sync.RWMutex // Mutex for thread-safe operations
|
||||
onChange func(T) error // Callback function when configuration changes
|
||||
converter func(data any, target *T) error // Optional custom converter function
|
||||
watchErrorFunc func(ctx context.Context, err error) // Optional error handling function for watch operations
|
||||
reuse bool // reuse the same target struct, default is false to avoid data race
|
||||
watcherName string // watcher name
|
||||
}
|
||||
|
||||
// NewLoader creates a new Loader instance
|
||||
// config: the configuration instance to watch for changes
|
||||
// propertyKey: the property key pattern to watch (use "" or "." to watch all configuration)
|
||||
// targetStruct: pointer to the struct that will receive the configuration values
|
||||
func NewLoader[T any](config *Config, propertyKey string, targetStruct ...*T) *Loader[T] {
|
||||
if len(targetStruct) > 0 {
|
||||
return &Loader[T]{
|
||||
config: config,
|
||||
propertyKey: propertyKey,
|
||||
targetStruct: targetStruct[0],
|
||||
reuse: false,
|
||||
}
|
||||
}
|
||||
return &Loader[T]{
|
||||
config: config,
|
||||
propertyKey: propertyKey,
|
||||
targetStruct: new(T),
|
||||
reuse: false,
|
||||
}
|
||||
}
|
||||
|
||||
// NewLoaderWithAdapter creates a new Loader instance
|
||||
// adapter: the adapter instance to use for loading and watching configuration
|
||||
// propertyKey: the property key pattern to watch (use "" or "." to watch all configuration)
|
||||
// targetStruct: pointer to the struct that will receive the configuration values
|
||||
func NewLoaderWithAdapter[T any](adapter Adapter, propertyKey string, targetStruct ...*T) *Loader[T] {
|
||||
return NewLoader(NewWithAdapter(adapter), propertyKey, targetStruct...)
|
||||
}
|
||||
|
||||
// OnChange sets the callback function that will be called when configuration changes
|
||||
// The callback function receives the updated configuration struct and can return an error
|
||||
func (l *Loader[T]) OnChange(fn func(updated T) error) *Loader[T] {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
l.onChange = fn
|
||||
return l
|
||||
}
|
||||
|
||||
// Load loads configuration from the config instance and binds it to the target struct
|
||||
// The context is passed to the underlying configuration adapter
|
||||
func (l *Loader[T]) Load(ctx context.Context) error {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
// Get configuration data
|
||||
var data *gvar.Var
|
||||
if l.propertyKey == "" || l.propertyKey == "." {
|
||||
// Get all configuration data
|
||||
configData, err := l.config.Data(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = gvar.New(configData)
|
||||
} else {
|
||||
// Get specific property
|
||||
configValue, err := l.config.Get(ctx, l.propertyKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if configValue != nil {
|
||||
data = configValue
|
||||
} else {
|
||||
data = gvar.New(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Use custom converter if provided, otherwise use default gconv.Scan
|
||||
if l.converter != nil && data != nil {
|
||||
if l.reuse {
|
||||
if err := l.converter(data.Val(), l.targetStruct); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var newConfig T
|
||||
if err := l.converter(data.Val(), &newConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
l.targetStruct = &newConfig
|
||||
}
|
||||
} else {
|
||||
if data != nil {
|
||||
if l.reuse {
|
||||
if err := data.Scan(l.targetStruct); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var newConfig T
|
||||
if err := data.Scan(&newConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
l.targetStruct = &newConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call change callback if exists
|
||||
if l.onChange != nil {
|
||||
return l.onChange(*l.targetStruct)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustLoad is like Load but panics if there is an error
|
||||
func (l *Loader[T]) MustLoad(ctx context.Context) {
|
||||
if err := l.Load(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Watch starts watching for configuration changes and automatically updates the target struct
|
||||
// name: the name of the watcher, which is used to identify this watcher
|
||||
// This method sets up a watcher that will call Load() when configuration changes are detected
|
||||
func (l *Loader[T]) Watch(ctx context.Context, name string) error {
|
||||
if name == "" {
|
||||
return gerror.New("Watcher name cannot be empty")
|
||||
}
|
||||
adapter := l.config.GetAdapter()
|
||||
if watcherAdapter, ok := adapter.(WatcherAdapter); ok {
|
||||
watcherAdapter.AddWatcher(name, func(ctx context.Context) {
|
||||
// Reload configuration when change is detected
|
||||
if err := l.Load(ctx); err != nil {
|
||||
// Use the configured error handler if available, otherwise execute default logging
|
||||
if l.watchErrorFunc != nil {
|
||||
l.watchErrorFunc(ctx, err)
|
||||
} else {
|
||||
// Default logging using intlog (internal logging for development)
|
||||
intlog.Errorf(ctx, "Configuration load failed in watcher %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
l.watcherName = name
|
||||
return nil
|
||||
}
|
||||
return gerror.New("Watcher adapter not found")
|
||||
}
|
||||
|
||||
// MustWatch is like Watch but panics if there is an error
|
||||
func (l *Loader[T]) MustWatch(ctx context.Context, name string) {
|
||||
if err := l.Watch(ctx, name); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// MustLoadAndWatch is a convenience method that calls MustLoad and MustWatch
|
||||
func (l *Loader[T]) MustLoadAndWatch(ctx context.Context, name string) {
|
||||
l.MustLoad(ctx)
|
||||
l.MustWatch(ctx, name)
|
||||
}
|
||||
|
||||
// Get returns the current configuration struct
|
||||
// This method is thread-safe and returns a copy of the current configuration
|
||||
func (l *Loader[T]) Get() T {
|
||||
l.mutex.RLock()
|
||||
defer l.mutex.RUnlock()
|
||||
return *l.targetStruct
|
||||
}
|
||||
|
||||
// GetPointer returns a pointer to the current configuration struct
|
||||
// This method is thread-safe and returns a pointer to the current configuration
|
||||
// The returned pointer is safe for read operations but should not be modified
|
||||
func (l *Loader[T]) GetPointer() *T {
|
||||
l.mutex.RLock()
|
||||
defer l.mutex.RUnlock()
|
||||
return l.targetStruct
|
||||
}
|
||||
|
||||
// SetConverter sets a custom converter function that will be used during Load operations
|
||||
// The converter function receives the source data and the target struct pointer
|
||||
func (l *Loader[T]) SetConverter(converter func(data any, target *T) error) *Loader[T] {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
l.converter = converter
|
||||
return l
|
||||
}
|
||||
|
||||
// SetWatchErrorHandler sets an error handling function that will be called when Load operations fail during Watch
|
||||
func (l *Loader[T]) SetWatchErrorHandler(errorFunc func(ctx context.Context, err error)) *Loader[T] {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
l.watchErrorFunc = errorFunc
|
||||
return l
|
||||
}
|
||||
|
||||
// SetReuseTargetStruct sets whether to reuse the same target struct or create a new one on updates
|
||||
func (l *Loader[T]) SetReuseTargetStruct(reuse bool) *Loader[T] {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
l.reuse = reuse
|
||||
return l
|
||||
}
|
||||
|
||||
// StopWatch stops watching for configuration changes and removes the associated watcher
|
||||
func (l *Loader[T]) StopWatch(ctx context.Context) (bool, error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if l.watcherName == "" {
|
||||
return false, gerror.New("No watcher name specified")
|
||||
}
|
||||
adapter := l.config.GetAdapter()
|
||||
if watcherAdapter, ok := adapter.(WatcherAdapter); ok {
|
||||
watcherAdapter.RemoveWatcher(l.watcherName)
|
||||
l.watcherName = ""
|
||||
return true, nil
|
||||
}
|
||||
return false, gerror.New("Watcher adapter not found")
|
||||
}
|
||||
|
||||
// IsWatching returns true if the loader is currently watching for configuration changes
|
||||
func (l *Loader[T]) IsWatching() bool {
|
||||
l.mutex.RLock()
|
||||
defer l.mutex.RUnlock()
|
||||
if l.watcherName == "" {
|
||||
return false
|
||||
}
|
||||
adapter := l.config.GetAdapter()
|
||||
if watcherAdapter, ok := adapter.(WatcherAdapter); ok {
|
||||
return watcherAdapter.IsWatching(l.watcherName)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -17,18 +17,23 @@ import (
|
||||
// It provides a unified implementation of watcher management to avoid code duplication
|
||||
// across different adapter implementations.
|
||||
type WatcherRegistry struct {
|
||||
watchers *gmap.StrAnyMap // Watchers map storing watcher callbacks.
|
||||
watchers *gmap.KVMap[string, WatcherFunc] // Watchers map storing watcher callbacks.
|
||||
}
|
||||
|
||||
// NewWatcherRegistry creates and returns a new WatcherRegistry instance.
|
||||
func NewWatcherRegistry() *WatcherRegistry {
|
||||
return &WatcherRegistry{
|
||||
watchers: gmap.NewStrAnyMap(true),
|
||||
watchers: gmap.NewKVMap[string, WatcherFunc](true),
|
||||
}
|
||||
}
|
||||
|
||||
// IsWatching checks whether the watcher with the specified name is registered.
|
||||
func (r *WatcherRegistry) IsWatching(name string) bool {
|
||||
return r.watchers.Contains(name)
|
||||
}
|
||||
|
||||
// Add adds a watcher with the specified name and callback function.
|
||||
func (r *WatcherRegistry) Add(name string, fn func(ctx context.Context)) {
|
||||
func (r *WatcherRegistry) Add(name string, fn WatcherFunc) {
|
||||
r.watchers.Set(name, fn)
|
||||
}
|
||||
|
||||
@ -46,17 +51,15 @@ func (r *WatcherRegistry) GetNames() []string {
|
||||
// Each callback is executed in a separate goroutine with panic recovery to prevent
|
||||
// one watcher's panic from affecting others.
|
||||
func (r *WatcherRegistry) Notify(ctx context.Context) {
|
||||
r.watchers.Iterator(func(k string, v any) bool {
|
||||
if fn, ok := v.(func(ctx context.Context)); ok {
|
||||
go func(k string, fn func(ctx context.Context), ctx context.Context) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
intlog.Errorf(ctx, "watcher %s panic: %v", k, r)
|
||||
}
|
||||
}()
|
||||
fn(ctx)
|
||||
}(k, fn, ctx)
|
||||
}
|
||||
r.watchers.Iterator(func(k string, fn WatcherFunc) bool {
|
||||
go func(k string, fn WatcherFunc, ctx context.Context) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
intlog.Errorf(ctx, "watcher %s panic: %v", k, r)
|
||||
}
|
||||
}()
|
||||
fn(ctx)
|
||||
}(k, fn, ctx)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
345
os/gcfg/gcfg_z_unit_loader_test.go
Normal file
345
os/gcfg/gcfg_z_unit_loader_test.go
Normal file
@ -0,0 +1,345 @@
|
||||
// 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 gcfg_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
// TestConfig is a test struct for configuration binding
|
||||
type TestConfig struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Age int `json:"age" yaml:"age"`
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
Features []string `json:"features" yaml:"features"`
|
||||
Server ServerConfig `json:"server" yaml:"server"`
|
||||
}
|
||||
|
||||
// TestConfig2 is a test struct for configuration binding
|
||||
type TestConfig2 struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Age int `json:"age" yaml:"age"`
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
Features string `json:"features" yaml:"features"`
|
||||
Server ServerConfig `json:"server" yaml:"server"`
|
||||
}
|
||||
|
||||
// TestConfig3 is a test struct for configuration binding
|
||||
type TestConfig3 struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Age int `json:"age" yaml:"age"`
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
Features []string `json:"features" yaml:"features"`
|
||||
Server ServerConfig `json:"server" yaml:"server"`
|
||||
Other string `json:"other" yaml:"other"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port int `json:"port" yaml:"port"`
|
||||
}
|
||||
|
||||
var configContent = `
|
||||
name: "test-app"
|
||||
age: 25
|
||||
enabled: true
|
||||
features: ["feature1", "feature2", "feature3"]
|
||||
server:
|
||||
host: "localhost"
|
||||
port: 8080
|
||||
`
|
||||
|
||||
func TestLoader_Load(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
configFile = "./" + guid.S() + ".yaml"
|
||||
err = gfile.PutContents(configFile, configContent)
|
||||
)
|
||||
t.AssertNil(err)
|
||||
defer gfile.RemoveFile(configFile)
|
||||
|
||||
// Create a new config instance
|
||||
cfg, err := gcfg.NewAdapterFile(configFile)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Create loader
|
||||
loader := gcfg.NewLoaderWithAdapter[TestConfig](cfg, "")
|
||||
|
||||
// Load configuration
|
||||
err = loader.Load(context.Background())
|
||||
t.AssertNil(err)
|
||||
v := loader.Get()
|
||||
|
||||
// Check loaded values
|
||||
t.Assert(v.Name, "test-app")
|
||||
t.Assert(v.Age, 25)
|
||||
t.Assert(v.Enabled, true)
|
||||
t.Assert(v.Server.Host, "localhost")
|
||||
t.Assert(v.Server.Port, 8080)
|
||||
t.Assert(len(v.Features), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_LoadWithDefaultValues(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
configFile = "./" + guid.S() + ".yaml"
|
||||
err = gfile.PutContents(configFile, configContent)
|
||||
)
|
||||
t.AssertNil(err)
|
||||
defer gfile.RemoveFile(configFile)
|
||||
|
||||
// Create a new config instance
|
||||
cfg, err := gcfg.NewAdapterFile(configFile)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Create target struct
|
||||
var targetConfig TestConfig3
|
||||
targetConfig.Other = "other"
|
||||
|
||||
// Create loader
|
||||
loader := gcfg.NewLoaderWithAdapter(cfg, "", &targetConfig)
|
||||
loader.SetReuseTargetStruct(true)
|
||||
|
||||
// Load configuration
|
||||
err = loader.Load(context.Background())
|
||||
t.AssertNil(err)
|
||||
v := loader.Get()
|
||||
|
||||
// Check loaded values
|
||||
t.Assert(v.Name, "test-app")
|
||||
t.Assert(v.Age, 25)
|
||||
t.Assert(v.Enabled, true)
|
||||
t.Assert(v.Server.Host, "localhost")
|
||||
t.Assert(v.Server.Port, 8080)
|
||||
t.Assert(len(v.Features), 3)
|
||||
t.Assert(v.Other, "other")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_LoadWithPropertyKey(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
configFile = "./" + guid.S() + ".yaml"
|
||||
err = gfile.PutContents(configFile, configContent)
|
||||
)
|
||||
t.AssertNil(err)
|
||||
defer gfile.RemoveFile(configFile)
|
||||
|
||||
// Create a new config instance
|
||||
cfg, err := gcfg.NewAdapterFile(configFile)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Create loader with specific property key
|
||||
loader := gcfg.NewLoaderWithAdapter[ServerConfig](cfg, "server")
|
||||
|
||||
// Load configuration
|
||||
err = loader.Load(context.Background())
|
||||
t.AssertNil(err)
|
||||
v := loader.Get()
|
||||
|
||||
// Check loaded values - only the app section should be loaded
|
||||
t.Assert(v.Host, "localhost")
|
||||
t.Assert(v.Port, 8080)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_WatchAndOnChange(t *testing.T) {
|
||||
var configContent2 = `
|
||||
name: test-app-2
|
||||
age: 200
|
||||
enabled: true
|
||||
features: ["feature1", "feature2", "feature3"]
|
||||
server:
|
||||
host: localhost
|
||||
port: 8080
|
||||
`
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a new config instance
|
||||
cfg, err := gcfg.NewAdapterContent(configContent)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Variable to track if callback was called
|
||||
callbackCalled := gtype.NewBool(false)
|
||||
|
||||
// Create loader
|
||||
loader := gcfg.NewLoaderWithAdapter[TestConfig](cfg, "")
|
||||
|
||||
// Set change callback
|
||||
loader.OnChange(func(updated TestConfig) error {
|
||||
callbackCalled.Set(true)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Load configuration
|
||||
err = loader.Load(context.Background())
|
||||
t.AssertNil(err)
|
||||
err = loader.Watch(context.Background(), "test-watcher")
|
||||
t.AssertNil(err)
|
||||
v := loader.Get()
|
||||
t.Assert(v.Name, "test-app")
|
||||
t.Assert(v.Age, 25)
|
||||
err = cfg.SetContent(configContent2)
|
||||
t.AssertNil(err)
|
||||
time.Sleep(2 * time.Second)
|
||||
v2 := loader.Get()
|
||||
t.Assert(v2.Name, "test-app-2")
|
||||
t.Assert(v2.Age, 200)
|
||||
t.Assert(callbackCalled.Val(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_SetConverter(t *testing.T) {
|
||||
var configContent2 = `
|
||||
name: test-app-2
|
||||
age: 200
|
||||
enabled: true
|
||||
features: ["feature", "feature", "feature"]
|
||||
server:
|
||||
host: localhost
|
||||
port: 8080
|
||||
`
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
configFile = "./" + guid.S() + ".yaml"
|
||||
err = gfile.PutContents(configFile, configContent2)
|
||||
)
|
||||
t.AssertNil(err)
|
||||
defer gfile.RemoveFile(configFile)
|
||||
|
||||
// Create a new config instance
|
||||
cfg, err := gcfg.NewAdapterFile(configFile)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Create loader
|
||||
loader := gcfg.NewLoaderWithAdapter[TestConfig2](cfg, "features")
|
||||
|
||||
// Set custom converter
|
||||
loader.SetConverter(func(data any, target *TestConfig2) error {
|
||||
s := gconv.Strings(data)
|
||||
target.Features = strings.Join(s, ",")
|
||||
return nil
|
||||
})
|
||||
|
||||
// Load configuration
|
||||
err = loader.Load(context.Background())
|
||||
t.AssertNil(err)
|
||||
v := loader.Get()
|
||||
|
||||
// Check converted values
|
||||
t.Assert(v.Features, "feature,feature,feature")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_SetWatchErrorHandler(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a new config instance with content that will cause converter error
|
||||
cfg, err := gcfg.NewAdapterContent(configContent)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Create loader
|
||||
loader := gcfg.NewLoaderWithAdapter[TestConfig](cfg, "")
|
||||
|
||||
// Set error handler for watch operations
|
||||
errorHandled := gtype.NewBool(false)
|
||||
loader.SetWatchErrorHandler(func(ctx context.Context, err error) {
|
||||
errorHandled.Set(true)
|
||||
})
|
||||
|
||||
// Set a converter that will fail
|
||||
loader.SetConverter(func(data any, target *TestConfig) error {
|
||||
return errors.New("converter error")
|
||||
})
|
||||
|
||||
// Load initially - this should return error without calling error handler
|
||||
err = loader.Load(context.Background())
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Error(), "converter error")
|
||||
// Error handler should NOT be called during direct Load
|
||||
t.Assert(errorHandled.Val(), false)
|
||||
|
||||
// Start watching - now errors during Load should trigger the error handler
|
||||
err = loader.Watch(context.Background(), "test-error-handler")
|
||||
t.AssertNil(err)
|
||||
// Reset
|
||||
errorHandled.Set(false)
|
||||
// Trigger a config change - this will call Load internally and should trigger error handler
|
||||
err = cfg.SetContent(configContent)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Wait for watcher to process the change
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Error handler should be called during Watch's Load
|
||||
t.Assert(errorHandled.Val(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_IsWatchingAndStopWatch(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a new config instance
|
||||
cfg, err := gcfg.NewAdapterContent(configContent)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Create loader
|
||||
loader := gcfg.NewLoaderWithAdapter[TestConfig](cfg, "")
|
||||
|
||||
// Initially, should not be watching
|
||||
t.Assert(loader.IsWatching(), false)
|
||||
|
||||
// Load configuration
|
||||
err = loader.Load(context.Background())
|
||||
t.AssertNil(err)
|
||||
|
||||
// Start watching
|
||||
err = loader.Watch(context.Background(), "test-stopwatch-watcher")
|
||||
t.AssertNil(err)
|
||||
|
||||
// Now should be watching
|
||||
t.Assert(loader.IsWatching(), true)
|
||||
|
||||
// Stop watching
|
||||
stopped, err := loader.StopWatch(context.Background())
|
||||
t.AssertNil(err)
|
||||
t.Assert(stopped, true)
|
||||
|
||||
// Should not be watching anymore
|
||||
t.Assert(loader.IsWatching(), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_StopWatchWithoutWatcher(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a new config instance
|
||||
cfg, err := gcfg.NewAdapterContent(configContent)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Create loader without starting to watch
|
||||
loader := gcfg.NewLoaderWithAdapter[TestConfig](cfg, "")
|
||||
|
||||
// Initially, should not be watching
|
||||
t.Assert(loader.IsWatching(), false)
|
||||
|
||||
// Try to stop watching when not watching
|
||||
stopped, err := loader.StopWatch(context.Background())
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(stopped, false)
|
||||
t.Assert(err.Error(), "No watcher name specified")
|
||||
})
|
||||
}
|
||||
@ -76,7 +76,7 @@ var (
|
||||
}
|
||||
// regular expression object for single rule
|
||||
// which is compiled just once and of repeatable usage.
|
||||
ruleRegex, _ = regexp.Compile(singleRulePattern)
|
||||
ruleRegex = regexp.MustCompile(singleRulePattern)
|
||||
|
||||
// decorativeRuleMap defines all rules that are just marked rules which have neither functional meaning
|
||||
// nor error messages.
|
||||
|
||||
@ -2,5 +2,5 @@ package gf
|
||||
|
||||
const (
|
||||
// VERSION is the current GoFrame version.
|
||||
VERSION = "v2.9.8"
|
||||
VERSION = "v2.10.0"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user