Compare commits

...

12 Commits

Author SHA1 Message Date
455830b842 test(database/gdb): add more unit testing cases for Raw feature (#3962) 2024-11-23 18:35:02 +08:00
e56371e7c9 fix(cmd/gf): incorrect environment variables printing before cli does some environment changes (#3961) 2024-11-23 17:54:20 +08:00
eb8024913d feat(net/goai): add min, max, length, min-length, max-length and between support for OpenAPIv3 (#3914) 2024-11-23 15:38:52 +08:00
ee3eb8d48c fix(net/goai): embedded struct with tags also expand as properties of the parent struct (#3956) 2024-11-23 15:38:14 +08:00
15f94975db feat(net/goai): add enhanced response status interface (#3896) 2024-11-23 15:37:46 +08:00
3797d0eee4 feat: new version v2.8.1 (#3950) 2024-11-21 20:51:46 +08:00
e9ce1bde87 fix(database/gdb): incompatitable statement like Order("id", "dasc") (#3949) 2024-11-21 15:08:29 +08:00
9c8b21af7b fix(cmd/gf): incorrect make command in project template (#3946) 2024-11-20 21:47:32 +08:00
90851881cc fix(registry/zookeeper): watch service name path error with extra suffix - (#3948) 2024-11-20 20:39:05 +08:00
3090fe7f4e fix(database/gdb): recognize tinyint(1)/int(1) as bool (#3943) 2024-11-20 20:21:33 +08:00
0d1aed0741 fix(os/gcache): function Remove returns value not *gvar.Var as previous version (#3936) 2024-11-19 23:30:29 +08:00
910703ec3a chore(database/gdb): comment update for function Model.Order (#3933) 2024-11-18 18:33:59 +08:00
53 changed files with 420 additions and 153 deletions

View File

@ -3,13 +3,13 @@ module github.com/gogf/gf/cmd/gf/v2
go 1.18
require (
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.8.1
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.8.1
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.8.1
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.8.1
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.8.1
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.8.1
github.com/gogf/gf/v2 v2.8.1
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f
github.com/olekukonko/tablewriter v0.0.5
golang.org/x/mod v0.17.0

View File

@ -138,11 +138,6 @@ type cBuildInput struct {
type cBuildOutput struct{}
func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
// print used go env
if in.DumpENV {
_, _ = Env.Index(ctx, cEnvInput{})
}
mlog.SetHeaderPrint(true)
mlog.Debugf(`build command input: %+v`, in)
@ -241,6 +236,10 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
} else {
genv.MustSet("CGO_ENABLED", "0")
}
// print used go env
if in.DumpENV {
_, _ = Env.Index(ctx, cEnvInput{})
}
for system, item := range platformMap {
if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) {
continue

View File

@ -2,7 +2,7 @@ module github.com/gogf/gf/cmd/gf/cmd/gf/testdata/vardump/v2
go 1.18
require github.com/gogf/gf/v2 v2.8.0
require github.com/gogf/gf/v2 v2.8.1
require (
go.opentelemetry.io/otel v1.24.0 // indirect

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@ go 1.20
require (
github.com/apolloconfig/agollo/v4 v4.3.1
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
)
require (

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/consul/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/hashicorp/consul/api v1.24.0
github.com/hashicorp/go-cleanhttp v0.5.2
)

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/kubecm/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
k8s.io/api v0.27.4
k8s.io/apimachinery v0.27.4
k8s.io/client-go v0.27.4

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/nacos/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/nacos-group/nacos-sdk-go/v2 v2.2.5
)

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/polaris/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/polarismesh/polaris-go v1.5.8
)

View File

@ -4,7 +4,7 @@ go 1.20
require (
github.com/ClickHouse/clickhouse-go/v2 v2.0.15
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/google/uuid v1.3.0
github.com/shopspring/decimal v1.3.1
)

View File

@ -6,7 +6,7 @@ replace github.com/gogf/gf/v2 => ../../../
require (
gitee.com/chunanyong/dm v1.8.12
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
)
require (

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/mssql/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/microsoft/go-mssqldb v1.7.1
)

View File

@ -4,7 +4,7 @@ go 1.20
require (
github.com/go-sql-driver/mysql v1.7.1
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
)
require (

View File

@ -7,6 +7,7 @@
package mysql_test
import (
"context"
"testing"
"github.com/gogf/gf/v2/database/gdb"
@ -14,7 +15,7 @@ import (
"github.com/gogf/gf/v2/test/gtest"
)
func Test_Insert_Raw(t *testing.T) {
func Test_Raw_Insert(t *testing.T) {
table := createTable()
defer dropTable(table)
@ -33,7 +34,7 @@ func Test_Insert_Raw(t *testing.T) {
})
}
func Test_BatchInsert_Raw(t *testing.T) {
func Test_Raw_BatchInsert(t *testing.T) {
table := createTable()
defer dropTable(table)
@ -63,7 +64,7 @@ func Test_BatchInsert_Raw(t *testing.T) {
})
}
func Test_Update_Raw(t *testing.T) {
func Test_Raw_Update(t *testing.T) {
table := createInitTable()
defer dropTable(table)
@ -84,3 +85,45 @@ func Test_Update_Raw(t *testing.T) {
t.Assert(n, 1)
})
}
func Test_Raw_Where(t *testing.T) {
table1 := createTable("Test_Raw_Where_Table1")
table2 := createTable("Test_Raw_Where_Table2")
defer dropTable(table1)
defer dropTable(table2)
// https://github.com/gogf/gf/issues/3922
gtest.C(t, func(t *gtest.T) {
expectSql := "SELECT * FROM `Test_Raw_Where_Table1` AS A WHERE NOT EXISTS (SELECT B.id FROM `Test_Raw_Where_Table2` AS B WHERE `B`.`id`=A.id) LIMIT 1"
sql, err := gdb.ToSQL(ctx, func(ctx context.Context) error {
s := db.Model(table2).As("B").Ctx(ctx).Fields("B.id").Where("B.id", gdb.Raw("A.id"))
m := db.Model(table1).As("A").Ctx(ctx).Where("NOT EXISTS ?", s).Limit(1)
_, err := m.All()
return err
})
t.AssertNil(err)
t.Assert(expectSql, sql)
})
gtest.C(t, func(t *gtest.T) {
expectSql := "SELECT * FROM `Test_Raw_Where_Table1` AS A WHERE NOT EXISTS (SELECT B.id FROM `Test_Raw_Where_Table2` AS B WHERE B.id=A.id) LIMIT 1"
sql, err := gdb.ToSQL(ctx, func(ctx context.Context) error {
s := db.Model(table2).As("B").Ctx(ctx).Fields("B.id").Where(gdb.Raw("B.id=A.id"))
m := db.Model(table1).As("A").Ctx(ctx).Where("NOT EXISTS ?", s).Limit(1)
_, err := m.All()
return err
})
t.AssertNil(err)
t.Assert(expectSql, sql)
})
// https://github.com/gogf/gf/issues/3915
gtest.C(t, func(t *gtest.T) {
expectSql := "SELECT * FROM `Test_Raw_Where_Table1` WHERE `passport` < `nickname`"
sql, err := gdb.ToSQL(ctx, func(ctx context.Context) error {
m := db.Model(table1).Ctx(ctx).WhereLT("passport", gdb.Raw("`nickname`"))
_, err := m.All()
return err
})
t.AssertNil(err)
t.Assert(expectSql, sql)
})
}

View File

@ -1345,3 +1345,35 @@ func Test_Issue3626(t *testing.T) {
t.Assert(count, 1)
})
}
// https://github.com/gogf/gf/issues/3932
func Test_Issue3932(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
one, err := db.Model(table).Order("id", "desc").One()
t.AssertNil(err)
t.Assert(one["id"], 10)
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Model(table).Order("id desc").One()
t.AssertNil(err)
t.Assert(one["id"], 10)
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Model(table).Order("id desc, nickname asc").One()
t.AssertNil(err)
t.Assert(one["id"], 10)
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Model(table).Order("id desc", "nickname asc").One()
t.AssertNil(err)
t.Assert(one["id"], 10)
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Model(table).Order("id desc").Order("nickname asc").One()
t.AssertNil(err)
t.Assert(one["id"], 10)
})
}

View File

@ -1293,7 +1293,7 @@ func Test_Model_OrderBy(t *testing.T) {
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Order("NULL").All()
result, err := db.Model(table).Order(gdb.Raw("NULL")).All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["nickname"].String(), "name_1")

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/oracle/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/sijms/go-ora/v2 v2.7.10
)

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/pgsql/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/lib/pq v1.10.9
)

View File

@ -4,7 +4,7 @@ go 1.20
require (
github.com/glebarez/go-sqlite v1.21.2
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
)
require (

View File

@ -1431,7 +1431,7 @@ func Test_Model_OrderBy(t *testing.T) {
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Order("NULL").All()
result, err := db.Model(table).Order(gdb.Raw("NULL")).All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["nickname"].String(), "name_1")

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/mattn/go-sqlite3 v1.14.17
)

View File

@ -1390,7 +1390,7 @@ func Test_Model_OrderBy(t *testing.T) {
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Order("NULL").All()
result, err := db.Model(table).Order(gdb.Raw("NULL")).All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["nickname"].String(), "name_1")

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/metric/otelmetric/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/prometheus/client_golang v1.19.1
go.opentelemetry.io/contrib/instrumentation/runtime v0.49.0
go.opentelemetry.io/otel v1.24.0

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/nosql/redis/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/redis/go-redis/v9 v9.2.1
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/trace v1.24.0

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/etcd/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
go.etcd.io/etcd/client/v3 v3.5.7
google.golang.org/grpc v1.59.0
)

View File

@ -2,7 +2,7 @@ module github.com/gogf/gf/contrib/registry/file/v2
go 1.20
require github.com/gogf/gf/v2 v2.8.0
require github.com/gogf/gf/v2 v2.8.1
require (
github.com/BurntSushi/toml v1.4.0 // indirect

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/nacos/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/nacos-group/nacos-sdk-go/v2 v2.2.7
)

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/polaris/v2
go 1.21
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
github.com/polarismesh/polaris-go v1.5.8
)

View File

@ -4,7 +4,7 @@ go 1.20
require (
github.com/go-zookeeper/zk v1.0.3
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
golang.org/x/sync v0.7.0
)

View File

@ -65,7 +65,7 @@ func (r *Registry) Register(_ context.Context, service gsvc.Service) (gsvc.Servi
// Deregister off-lines and removes `service` from the Registry.
func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error {
ch := make(chan error, 1)
prefix := strings.TrimPrefix(strings.ReplaceAll(service.GetPrefix(), "/", "-"), "-")
prefix := strings.Trim(strings.ReplaceAll(service.GetPrefix(), "/", "-"), "-")
servicePath := path.Join(r.opts.namespace, prefix, service.GetName())
go func() {
err := r.conn.Delete(servicePath, -1)

View File

@ -66,7 +66,7 @@ func (w *watcher) Proceed() ([]gsvc.Service, error) {
}
func (w *watcher) getServicesByPrefix() ([]gsvc.Service, error) {
prefix := strings.TrimPrefix(strings.ReplaceAll(w.prefix, "/", "-"), "-")
prefix := strings.Trim(strings.ReplaceAll(w.prefix, "/", "-"), "-")
serviceNamePath := path.Join(w.nameSpace, prefix)
instances, err, _ := w.group.Do(serviceNamePath, func() (interface{}, error) {
servicesID, _, err := w.conn.Children(serviceNamePath)
@ -122,7 +122,7 @@ func (w *watcher) Close() error {
}
func (w *watcher) watch(ctx context.Context) {
prefix := strings.TrimPrefix(strings.ReplaceAll(w.prefix, "/", "-"), "-")
prefix := strings.Trim(strings.ReplaceAll(w.prefix, "/", "-"), "-")
serviceNamePath := path.Join(w.nameSpace, prefix)
for {

View File

@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/rpc/grpcx/v2
go 1.20
require (
github.com/gogf/gf/contrib/registry/file/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/contrib/registry/file/v2 v2.8.1
github.com/gogf/gf/v2 v2.8.1
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/trace v1.24.0
google.golang.org/grpc v1.64.1

View File

@ -2,7 +2,7 @@ module github.com/gogf/gf/contrib/sdk/httpclient/v2
go 1.20
require github.com/gogf/gf/v2 v2.8.0
require github.com/gogf/gf/v2 v2.8.1
require (
github.com/BurntSushi/toml v1.4.0 // indirect

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/otlpgrpc/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/trace/otlphttp/v2
go 1.20
require (
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.1
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0

View File

@ -259,10 +259,6 @@ func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fie
if gstr.ContainsI(fieldType, "unsigned") {
return LocalTypeUint, nil
}
// field length is 1 means boolean.
if typePattern == "1" {
return LocalTypeBool, nil
}
return LocalTypeInt, nil
case

View File

@ -15,28 +15,42 @@ import (
// Order sets the "ORDER BY" statement for the model.
//
// Eg:
// Example:
// Order("id desc")
// Order("id", "desc").
// Order("id", "desc")
// Order("id desc,name asc")
// Order("id desc", "name asc")
// Order("id desc").Order("name asc")
// Order(gdb.Raw("field(id, 3,1,2)")).
func (m *Model) Order(orderBy ...interface{}) *Model {
if len(orderBy) == 0 {
return m
}
model := m.getModel()
if model.orderBy != "" {
model.orderBy += ","
}
var (
core = m.db.GetCore()
model = m.getModel()
)
for _, v := range orderBy {
if model.orderBy != "" {
model.orderBy += ","
}
switch v.(type) {
case Raw, *Raw:
model.orderBy += gconv.String(v)
return model
default:
orderByStr := gconv.String(v)
if gstr.Contains(orderByStr, " ") {
model.orderBy += core.QuoteString(orderByStr)
} else {
if gstr.Equal(orderByStr, "ASC") || gstr.Equal(orderByStr, "DESC") {
model.orderBy = gstr.TrimRight(model.orderBy, ",")
model.orderBy += " " + orderByStr
} else {
model.orderBy += core.QuoteWord(orderByStr)
}
}
}
}
model.orderBy += model.db.GetCore().QuoteString(gstr.JoinAny(orderBy, ", "))
return model
}
@ -68,10 +82,14 @@ func (m *Model) Group(groupBy ...string) *Model {
if len(groupBy) == 0 {
return m
}
model := m.getModel()
var (
core = m.db.GetCore()
model = m.getModel()
)
if model.groupBy != "" {
model.groupBy += ","
}
model.groupBy += model.db.GetCore().QuoteString(strings.Join(groupBy, ","))
model.groupBy += core.QuoteString(strings.Join(groupBy, ","))
return model
}

View File

@ -283,10 +283,12 @@ func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
}
}
// ScanAndCount scans a single record or record array that matches the given conditions and counts the total number of records that match those conditions.
// If useFieldForCount is true, it will use the fields specified in the model for counting;
// The pointer parameter is a pointer to a struct that the scanned data will be stored in.
// The pointerCount parameter is a pointer to an integer that will be set to the total number of records that match the given conditions.
// ScanAndCount scans a single record or record array that matches the given conditions and counts the total number
// of records that match those conditions.
//
// If `useFieldForCount` is true, it will use the fields specified in the model for counting;
// The `pointer` parameter is a pointer to a struct that the scanned data will be stored in.
// The `totalCount` parameter is a pointer to an integer that will be set to the total number of records that match the given conditions.
// The where parameter is an optional list of conditions to use when retrieving records.
//
// Example:

View File

@ -3,22 +3,22 @@ module github.com/gogf/gf/example
go 1.21
require (
github.com/gogf/gf/contrib/config/apollo/v2 v2.8.0
github.com/gogf/gf/contrib/config/consul/v2 v2.8.0
github.com/gogf/gf/contrib/config/kubecm/v2 v2.8.0
github.com/gogf/gf/contrib/config/nacos/v2 v2.8.0
github.com/gogf/gf/contrib/config/polaris/v2 v2.8.0
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.8.0
github.com/gogf/gf/contrib/metric/otelmetric/v2 v2.8.0
github.com/gogf/gf/contrib/nosql/redis/v2 v2.8.0
github.com/gogf/gf/contrib/registry/etcd/v2 v2.8.0
github.com/gogf/gf/contrib/registry/file/v2 v2.8.0
github.com/gogf/gf/contrib/registry/nacos/v2 v2.8.0
github.com/gogf/gf/contrib/registry/polaris/v2 v2.8.0
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.8.0
github.com/gogf/gf/contrib/trace/otlpgrpc/v2 v2.8.0
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.8.0
github.com/gogf/gf/v2 v2.8.0
github.com/gogf/gf/contrib/config/apollo/v2 v2.8.1
github.com/gogf/gf/contrib/config/consul/v2 v2.8.1
github.com/gogf/gf/contrib/config/kubecm/v2 v2.8.1
github.com/gogf/gf/contrib/config/nacos/v2 v2.8.1
github.com/gogf/gf/contrib/config/polaris/v2 v2.8.1
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.8.1
github.com/gogf/gf/contrib/metric/otelmetric/v2 v2.8.1
github.com/gogf/gf/contrib/nosql/redis/v2 v2.8.1
github.com/gogf/gf/contrib/registry/etcd/v2 v2.8.1
github.com/gogf/gf/contrib/registry/file/v2 v2.8.1
github.com/gogf/gf/contrib/registry/nacos/v2 v2.8.1
github.com/gogf/gf/contrib/registry/polaris/v2 v2.8.1
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.8.1
github.com/gogf/gf/contrib/trace/otlpgrpc/v2 v2.8.1
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.8.1
github.com/gogf/gf/v2 v2.8.1
github.com/hashicorp/consul/api v1.24.0
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/nacos-group/nacos-sdk-go/v2 v2.2.7

View File

@ -63,8 +63,14 @@ const (
)
const (
validationRuleKeyForRequired = `required`
validationRuleKeyForIn = `in:`
validationRuleKeyForRequired = `required`
validationRuleKeyForIn = `in:`
validationRuleKeyForMax = `max:`
validationRuleKeyForMin = `min:`
validationRuleKeyForLength = `length:`
validationRuleKeyForMaxLength = `max-length:`
validationRuleKeyForMinLength = `min-length:`
validationRuleKeyForBetween = `between:`
)
var (

View File

@ -52,6 +52,17 @@ func (e *Examples) applyExamplesFile(path string) error {
if err != nil {
return err
}
err = e.applyExamplesData(data)
if err != nil {
return err
}
return nil
}
func (e *Examples) applyExamplesData(data interface{}) error {
if empty.IsNil(e) || empty.IsNil(data) {
return nil
}
switch v := data.(type) {
case map[string]interface{}:

View File

@ -255,12 +255,12 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
// =================================================================================================================
// Other Responses.
// =================================================================================================================
if enhancedResponse, ok := outputObject.Interface().(ResponseStatusDef); ok {
for statusCode, data := range enhancedResponse.ResponseStatusMap() {
if enhancedResponse, ok := outputObject.Interface().(IEnhanceResponseStatus); ok {
for statusCode, data := range enhancedResponse.EnhanceResponseStatus() {
if statusCode < 100 || statusCode >= 600 {
return gerror.Newf("Invalid HTTP status code: %d", statusCode)
}
if data == nil {
if data.Response == nil {
continue
}
status := gconv.String(statusCode)

View File

@ -12,13 +12,22 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// StatusCode is http status for response.
type StatusCode = int
// EnhancedStatusCode is http status for response.
type EnhancedStatusCode = int
// ResponseStatusDef is used to enhance the documentation of the response.
// EnhancedStatusType is the structure for certain response status.
// Currently, it only supports `Response` and `Examples`.
// `Response` is the response structure
// `Examples` is the examples for the response, map[string]interface{}, []interface{} are supported.
type EnhancedStatusType struct {
Response any
Examples any
}
// IEnhanceResponseStatus is used to enhance the documentation of the response.
// Normal response structure could implement this interface to provide more information.
type ResponseStatusDef interface {
ResponseStatusMap() map[StatusCode]any
type IEnhanceResponseStatus interface {
EnhanceResponseStatus() map[EnhancedStatusCode]EnhancedStatusType
}
// Response is specified by OpenAPI/Swagger 3.0 standard.

View File

@ -26,7 +26,14 @@ type Responses map[string]ResponseRef
// object could be someObject.Interface()
// There may be some difference between someObject.Type() and reflect.TypeOf(object).
func (oai *OpenApiV3) getResponseFromObject(object interface{}, isDefault bool) (*Response, error) {
func (oai *OpenApiV3) getResponseFromObject(data interface{}, isDefault bool) (*Response, error) {
var object interface{}
enhancedResponse, isEnhanced := data.(EnhancedStatusType)
if isEnhanced {
object = enhancedResponse.Response
} else {
object = data
}
// Add object schema to oai
if err := oai.addSchema(object); err != nil {
return nil, err
@ -86,6 +93,14 @@ func (oai *OpenApiV3) getResponseFromObject(object interface{}, isDefault bool)
}
}
// Override examples from enhanced response.
if isEnhanced {
err := examples.applyExamplesData(enhancedResponse.Examples)
if err != nil {
return nil, err
}
}
// Generate response schema from input.
schemaRef, err := oai.getResponseSchemaRef(refInput)
if err != nil {

View File

@ -8,6 +8,7 @@ package goai
import (
"reflect"
"strings"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gset"
@ -176,7 +177,7 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
// struct.
structFields, _ := gstructs.Fields(gstructs.FieldsInput{
Pointer: object,
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
RecursiveOption: gstructs.RecursiveOptionEmbedded,
})
schema.Type = TypeObject
for _, structField := range structFields {
@ -204,6 +205,56 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
if validationRuleSet.Contains(validationRuleKeyForRequired) {
schema.Required = append(schema.Required, key)
}
// Extract validation rules to schema. like min, max, length
lstRules := gstr.Split(ref.Value.ValidationRules, "|")
for _, rule := range lstRules {
if strings.HasPrefix(rule, validationRuleKeyForMax) {
if ref.Value.Type == "integer" || ref.Value.Type == "number" {
f := gconv.Float64(rule[4:])
ref.Value.Max = &f
}
}
if strings.HasPrefix(rule, validationRuleKeyForMaxLength) {
maxlength := gconv.Uint64(rule[11:])
ref.Value.MaxLength = &maxlength
}
if strings.HasPrefix(rule, validationRuleKeyForMin) {
if ref.Value.Type == "integer" || ref.Value.Type == "number" {
f := gconv.Float64(rule[4:])
ref.Value.Min = &f
}
}
if strings.HasPrefix(rule, validationRuleKeyForMinLength) {
minlength := gconv.Uint64(rule[11:])
ref.Value.MinLength = minlength
}
if strings.HasPrefix(rule, validationRuleKeyForLength) {
lengthRule := gstr.Split(rule[7:], ",")
if len(lengthRule) == 2 {
minlength := gconv.Uint64(lengthRule[0])
ref.Value.MinLength = minlength
maxlength := gconv.Uint64(lengthRule[1])
ref.Value.MaxLength = &maxlength
}
}
if strings.HasPrefix(rule, validationRuleKeyForBetween) {
if ref.Value.Type == "integer" || ref.Value.Type == "number" {
lengthRule := gstr.Split(rule[8:], ",")
if len(lengthRule) == 2 {
minimum := gconv.Float64(lengthRule[0])
ref.Value.Min = &minimum
maximum := gconv.Float64(lengthRule[1])
ref.Value.Max = &maximum
}
}
}
}
}
if !isValidParameterName(key) {
ignoreProperties = append(ignoreProperties, key)

View File

@ -13,6 +13,7 @@ import (
"time"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/goai"
@ -119,71 +120,97 @@ func Test_Issue3135(t *testing.T) {
})
}
type Issue3747CommonRes struct {
type Issue3889CommonRes struct {
g.Meta `mime:"application/json"`
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
type Issue3747Req struct {
type Issue3889Req struct {
g.Meta `path:"/default" method:"post"`
Name string
}
type Issue3747Res struct {
g.Meta `status:"201" resEg:"testdata/Issue3747JsonFile/201.json"`
type Issue3889Res struct {
g.Meta `status:"201" resEg:"testdata/Issue3889JsonFile/201.json"`
Info string `json:"info" eg:"Created!"`
}
// Example case
type Issue3747Res401 struct {
g.Meta `resEg:"testdata/Issue3747JsonFile/401.json"`
}
type Issue3889Res401 struct{}
// Override case 1
type Issue3747Res402 struct {
type Issue3889Res402 struct {
g.Meta `mime:"application/json"`
}
// Override case 2
type Issue3747Res403 struct {
type Issue3889Res403 struct {
Code int `json:"code"`
Message string `json:"message"`
}
// Common response case
type Issue3747Res404 struct{}
type Issue3889Res404 struct{}
func (r Issue3747Res) ResponseStatusMap() map[goai.StatusCode]any {
return map[goai.StatusCode]any{
401: Issue3747Res401{},
402: Issue3747Res402{},
403: Issue3747Res403{},
404: Issue3747Res404{},
405: struct{}{},
407: interface{}(nil),
406: nil,
var Issue3889ErrorRes = map[int][]gcode.Code{
401: {
gcode.New(1, "Aha, 401 - 1", nil),
gcode.New(2, "Aha, 401 - 2", nil),
},
}
func (r Issue3889Res) EnhanceResponseStatus() map[goai.EnhancedStatusCode]goai.EnhancedStatusType {
Codes401 := Issue3889ErrorRes[401]
// iterate Codes401 to generate Examples
var Examples401 []interface{}
for _, code := range Codes401 {
example := Issue3889CommonRes{
Code: code.Code(),
Message: code.Message(),
Data: nil,
}
Examples401 = append(Examples401, example)
}
return map[goai.EnhancedStatusCode]goai.EnhancedStatusType{
401: {
Response: Issue3889Res401{},
Examples: Examples401,
},
402: {
Response: Issue3889Res402{},
},
403: {
Response: Issue3889Res403{},
},
404: {
Response: Issue3889Res404{},
},
500: {
Response: struct{}{},
},
501: {},
}
}
type Issue3747 struct{}
type Issue3889 struct{}
func (Issue3747) Default(ctx context.Context, req *Issue3747Req) (res *Issue3747Res, err error) {
res = &Issue3747Res{}
func (Issue3889) Default(ctx context.Context, req *Issue3889Req) (res *Issue3889Res, err error) {
res = &Issue3889Res{}
return
}
// https://github.com/gogf/gf/issues/3747
func Test_Issue3747(t *testing.T) {
// https://github.com/gogf/gf/issues/3889
func Test_Issue3889(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := g.Server(guid.S())
openapi := s.GetOpenApi()
openapi.Config.CommonResponse = Issue3747CommonRes{}
openapi.Config.CommonResponse = Issue3889CommonRes{}
openapi.Config.CommonResponseDataField = `Data`
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(
new(Issue3747),
new(Issue3889),
)
})
s.SetLogger(nil)
@ -205,29 +232,71 @@ func Test_Issue3747(t *testing.T) {
t.AssertNE(j.Get(`paths./default.post.responses.402`).String(), "")
t.AssertNE(j.Get(`paths./default.post.responses.403`).String(), "")
t.AssertNE(j.Get(`paths./default.post.responses.404`).String(), "")
t.AssertNE(j.Get(`paths./default.post.responses.405`).String(), "")
t.Assert(j.Get(`paths./default.post.responses.406`).String(), "")
t.Assert(j.Get(`paths./default.post.responses.407`).String(), "")
t.AssertNE(j.Get(`paths./default.post.responses.500`).String(), "")
t.Assert(j.Get(`paths./default.post.responses.501`).String(), "")
// Check content
commonResponseSchema := `{"properties":{"code":{"format":"int","type":"integer"},"data":{"properties":{},"type":"object"},"message":{"format":"string","type":"string"}},"type":"object"}`
Status201ExamplesContent := `{"code 1":{"value":{"code":1,"data":"Good","message":"Aha, 201 - 1"}},"code 2":{"value":{"code":2,"data":"Not Bad","message":"Aha, 201 - 2"}}}`
Status401ExamplesContent := `{"example 1":{"value":{"code":1,"data":null,"message":"Aha, 401 - 1"}},"example 2":{"value":{"code":2,"data":null,"message":"Aha, 401 - 2"}}}`
Status402SchemaContent := `{"$ref":"#/components/schemas/github.com.gogf.gf.v2.net.goai_test.Issue3747Res402"}`
Issue3747Res403Ref := `{"$ref":"#/components/schemas/github.com.gogf.gf.v2.net.goai_test.Issue3747Res403"}`
Status402SchemaContent := `{"$ref":"#/components/schemas/github.com.gogf.gf.v2.net.goai_test.Issue3889Res402"}`
Issue3889Res403Ref := `{"$ref":"#/components/schemas/github.com.gogf.gf.v2.net.goai_test.Issue3889Res403"}`
t.Assert(j.Get(`paths./default.post.responses.201.content.application/json.examples`).String(), Status201ExamplesContent)
t.Assert(j.Get(`paths./default.post.responses.401.content.application/json.examples`).String(), Status401ExamplesContent)
t.Assert(j.Get(`paths./default.post.responses.402.content.application/json.schema`).String(), Status402SchemaContent)
t.Assert(j.Get(`paths./default.post.responses.403.content.application/json.schema`).String(), Issue3747Res403Ref)
t.Assert(j.Get(`paths./default.post.responses.403.content.application/json.schema`).String(), Issue3889Res403Ref)
t.Assert(j.Get(`paths./default.post.responses.404.content.application/json.schema`).String(), commonResponseSchema)
t.Assert(j.Get(`paths./default.post.responses.405.content.application/json.schema`).String(), commonResponseSchema)
t.Assert(j.Get(`paths./default.post.responses.500.content.application/json.schema`).String(), commonResponseSchema)
api := s.GetOpenApi()
reqPath := "github.com.gogf.gf.v2.net.goai_test.Issue3747Res403"
reqPath := "github.com.gogf.gf.v2.net.goai_test.Issue3889Res403"
schema := api.Components.Schemas.Get(reqPath).Value
Issue3747Res403Schema := `{"properties":{"code":{"format":"int","type":"integer"},"message":{"format":"string","type":"string"}},"type":"object"}`
t.Assert(schema, Issue3747Res403Schema)
Issue3889Res403Schema := `{"properties":{"code":{"format":"int","type":"integer"},"message":{"format":"string","type":"string"}},"type":"object"}`
t.Assert(schema, Issue3889Res403Schema)
})
}
type Issue3930DefaultReq struct {
g.Meta `path:"/user/{id}" method:"get" tags:"User" summary:"Get one user"`
Id int64 `v:"required" dc:"user id"`
}
type Issue3930DefaultRes struct {
*Issue3930User `dc:"user"`
}
type Issue3930User struct {
Id uint `json:"id" orm:"id" description:"user id"` // user id
}
type Issue3930 struct{}
func (Issue3930) Default(ctx context.Context, req *Issue3930DefaultReq) (res *Issue3930DefaultRes, err error) {
res = &Issue3930DefaultRes{}
return
}
// https://github.com/gogf/gf/issues/3930
func Test_Issue3930(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := g.Server(guid.S())
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(
new(Issue3930),
)
})
s.SetLogger(nil)
s.SetOpenApiPath("/api.json")
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
var (
api = s.GetOpenApi()
reqPath = "github.com.gogf.gf.v2.net.goai_test.Issue3930DefaultRes"
)
t.AssertNE(api.Components.Schemas.Get(reqPath).Value.Properties.Get("id"), nil)
})
}

View File

@ -1200,8 +1200,8 @@ func TestOpenApiV3_PathSecurity(t *testing.T) {
type Req struct {
gmeta.Meta `method:"PUT" security:"apiKey"` // 这里的apiKey要和openApi定义的key一致
Product string `json:"product" v:"required" description:"Unique product key"`
Name string `json:"name" v:"required" description:"Instance name"`
Product string `json:"product" v:"required" description:"Unique product key"`
Name string `json:"name" v:"required" description:"Instance name"`
}
type Res struct{}
@ -1345,3 +1345,35 @@ func Test_XExtension(t *testing.T) {
t.Assert(oai.String(), gtest.DataContent("XExtension", "expect.json"))
})
}
func Test_ValidationRules(t *testing.T) {
type Req struct {
g.Meta `path:"/rules" method:"POST" tags:"Rules" summary:"Validation rules."`
Name string `v:"required|min-length:3|max-length:32#required|min|max" dc:"Name"`
Age int `v:"required|min:1|max:100" dc:"Age"`
Grade int `v:"between:1,12#please enter the correct grade." dc:"Grade"`
Address string `v:"length:3,64" dc:"Address"`
}
gtest.C(t, func(t *gtest.T) {
var (
err error
oai = goai.New()
req = new(Req)
)
err = oai.Add(goai.AddInput{
Object: req,
})
t.AssertNil(err)
schema := oai.Components.Schemas.Get("github.com.gogf.gf.v2.net.goai_test.Req").Value
t.Assert(schema.Properties.Get("Name").Value.MinLength, 3)
t.Assert(schema.Properties.Get("Name").Value.MaxLength, 32)
t.Assert(schema.Properties.Get("Age").Value.Min, 1.0)
t.Assert(schema.Properties.Get("Age").Value.Max, 100.0)
t.Assert(schema.Properties.Get("Grade").Value.Min, 1.0)
t.Assert(schema.Properties.Get("Grade").Value.Max, 12.0)
t.Assert(schema.Properties.Get("Address").Value.MinLength, 3)
t.Assert(schema.Properties.Get("Address").Value.MaxLength, 64)
})
}

View File

@ -1,12 +0,0 @@
[
{
"code": 1,
"message": "Aha, 401 - 1",
"data": null
},
{
"code": 2,
"message": "Aha, 401 - 2",
"data": null
}
]

View File

@ -288,11 +288,7 @@ func (c *AdapterMemory) GetExpire(ctx context.Context, key interface{}) (time.Du
// If multiple keys are given, it returns the value of the last deleted item.
func (c *AdapterMemory) Remove(ctx context.Context, keys ...interface{}) (*gvar.Var, error) {
defer c.lru.Remove(keys...)
value, err := c.doRemove(ctx, keys...)
if err != nil {
return nil, err
}
return gvar.New(value), nil
return c.doRemove(ctx, keys...)
}
func (c *AdapterMemory) doRemove(_ context.Context, keys ...interface{}) (*gvar.Var, error) {

View File

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