Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue #2890 #3002

Merged
merged 1 commit into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 55 additions & 0 deletions net/ghttp/ghttp_z_unit_issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gtag"
"github.com/gogf/gf/v2/util/guid"
)

Expand Down Expand Up @@ -365,3 +366,57 @@ func Test_Issue2482(t *testing.T) {
t.Assert(c.PutContent(ctx, "/api/v2/order", content), `{"code":0,"message":"","data":null}`)
})
}

type Issue2890Enum string

const (
Issue2890EnumA Issue2890Enum = "a"
Issue2890EnumB Issue2890Enum = "b"
)

type Issue2890Req struct {
g.Meta `path:"/issue2890" method:"post"`
Id int
Enums Issue2890Enum `v:"required|enums"`
}

type Issue2890Res struct{}
type Issue2890Controller struct{}

func (c *Issue2890Controller) Post(ctx context.Context, req *Issue2890Req) (res *Issue2890Res, err error) {
g.RequestFromCtx(ctx).Response.Write(req.Enums)
return
}

// https://github.com/gogf/gf/issues/2890
func Test_Issue2890(t *testing.T) {
gtest.C(t, func(t *gtest.T) {

oldEnumsJson, err := gtag.GetGlobalEnums()
t.AssertNil(err)
defer t.AssertNil(gtag.SetGlobalEnums(oldEnumsJson))

err = gtag.SetGlobalEnums(`{"github.com/gogf/gf/v2/net/ghttp_test.Issue2890Enum": ["a","b"]}`)
t.AssertNil(err)

s := g.Server(guid.S())
s.Group("/api/v2", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(Issue2890Controller{})
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(1000 * time.Millisecond)

c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
t.Assert(
c.PostContent(ctx, "/api/v2/issue2890", ``),
`{"code":51,"message":"The Enums field is required","data":null}`,
)
t.Assert(
c.PostContent(ctx, "/api/v2/issue2890", `{"Enums":"c"}`),
"{\"code\":51,\"message\":\"The Enums value `c` should be in enums of: [\\\"a\\\",\\\"b\\\"]\",\"data\":null}")
})
}
3 changes: 2 additions & 1 deletion util/gvalid/gvalid.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ type fieldRule struct {
Name string // Alias name for the field.
Rule string // Rule string like: "max:6"
IsMeta bool // Is this rule is from gmeta.Meta, which marks it as whole struct rule.
FieldKind reflect.Kind // Kind of struct field, which is used for parameter type checks.
FieldKind reflect.Kind // Original kind of struct field, which is used for parameter type checks.
FieldType reflect.Type // Type of struct field, which is used for parameter type checks.
}

// iNoValidation is an interface that marks current struct not validated by package `gvalid`.
Expand Down
3 changes: 3 additions & 0 deletions util/gvalid/gvalid_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type RuleFuncInput struct {
// Field specifies the field for this rule to validate.
Field string

// ValueType specifies the type of the value, which might be nil.
ValueType reflect.Type

// Value specifies the value for this rule to validate.
Value *gvar.Var

Expand Down
13 changes: 7 additions & 6 deletions util/gvalid/gvalid_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ func (v *Validator) Run(ctx context.Context) Error {
}

return v.doCheckValue(ctx, doCheckValueInput{
Name: "",
Value: v.data,
Rule: gconv.String(v.rules),
Messages: v.messages,
DataRaw: v.assoc,
DataMap: gconv.Map(v.assoc),
Name: "",
Value: v.data,
ValueType: reflect.TypeOf(v.data),
Rule: gconv.String(v.rules),
Messages: v.messages,
DataRaw: v.assoc,
DataMap: gconv.Map(v.assoc),
})
}

Expand Down
13 changes: 7 additions & 6 deletions util/gvalid/gvalid_validator_check_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,13 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
}
// It checks each rule and its value in loop.
if validatedError := v.doCheckValue(ctx, doCheckValueInput{
Name: checkRuleItem.Name,
Value: value,
Rule: checkRuleItem.Rule,
Messages: customMessage[checkRuleItem.Name],
DataRaw: params,
DataMap: inputParamMap,
Name: checkRuleItem.Name,
Value: value,
ValueType: reflect.TypeOf(value),
Rule: checkRuleItem.Rule,
Messages: customMessage[checkRuleItem.Name],
DataRaw: params,
DataMap: inputParamMap,
}); validatedError != nil {
_, errorItem := validatedError.FirstItem()
// ===========================================================
Expand Down
16 changes: 9 additions & 7 deletions util/gvalid/gvalid_validator_check_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
checkRules = make([]fieldRule, 0)
nameToRuleMap = make(map[string]string) // just for internally searching index purpose.
customMessage = make(CustomMsg) // Custom rule error message map.
checkValueData = v.assoc // Ready to be validated data, which can be type of .
checkValueData = v.assoc // Ready to be validated data.
)
if checkValueData == nil {
checkValueData = object
Expand Down Expand Up @@ -181,6 +181,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
Rule: rule,
IsMeta: isMeta,
FieldKind: field.OriginalKind(),
FieldType: field.Type(),
})
}
} else {
Expand Down Expand Up @@ -302,12 +303,13 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
}
// It checks each rule and its value in loop.
if validatedError := v.doCheckValue(ctx, doCheckValueInput{
Name: checkRuleItem.Name,
Value: value,
Rule: checkRuleItem.Rule,
Messages: customMessage[checkRuleItem.Name],
DataRaw: checkValueData,
DataMap: inputParamMap,
Name: checkRuleItem.Name,
Value: value,
ValueType: checkRuleItem.FieldType,
Rule: checkRuleItem.Rule,
Messages: customMessage[checkRuleItem.Name],
DataRaw: checkValueData,
DataMap: inputParamMap,
}); validatedError != nil {
_, errorItem := validatedError.FirstItem()
// ============================================================
Expand Down
25 changes: 14 additions & 11 deletions util/gvalid/gvalid_validator_check_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ import (
)

type doCheckValueInput struct {
Name string // Name specifies the name of parameter `value`.
Value interface{} // Value specifies the value for the rules to be validated.
Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc.
Messages interface{} // Messages specifies the custom error messages for this rule from parameters input, which is usually type of map/slice.
DataRaw interface{} // DataRaw specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value.
DataMap map[string]interface{} // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally
Name string // Name specifies the name of parameter `value`.
Value interface{} // Value specifies the value for the rules to be validated.
ValueType reflect.Type // ValueType specifies the type of the value, mainly used for value type id retrieving.
Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc.
Messages interface{} // Messages specifies the custom error messages for this rule from parameters input, which is usually type of map/slice.
DataRaw interface{} // DataRaw specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value.
DataMap map[string]interface{} // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally
}

// doCheckValue does the really rules validation for single key-value.
Expand Down Expand Up @@ -146,11 +147,12 @@ func (v *Validator) doCheckValue(ctx context.Context, in doCheckValueInput) Erro
// Custom validation rules.
case customRuleFunc != nil:
err = customRuleFunc(ctx, RuleFuncInput{
Rule: ruleItems[index],
Message: message,
Field: in.Name,
Value: gvar.New(value),
Data: gvar.New(in.DataRaw),
Rule: ruleItems[index],
Message: message,
Field: in.Name,
ValueType: in.ValueType,
Value: gvar.New(value),
Data: gvar.New(in.DataRaw),
})

// Builtin validation rules.
Expand All @@ -159,6 +161,7 @@ func (v *Validator) doCheckValue(ctx context.Context, in doCheckValueInput) Erro
RuleKey: ruleKey,
RulePattern: rulePattern,
Field: in.Name,
ValueType: in.ValueType,
Value: gvar.New(value),
Data: gvar.New(in.DataRaw),
Message: message,
Expand Down
17 changes: 10 additions & 7 deletions util/gvalid/internal/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
package builtin

import (
"reflect"

"github.com/gogf/gf/v2/container/gvar"
)

Expand All @@ -26,13 +28,14 @@ type Rule interface {
}

type RunInput struct {
RuleKey string // RuleKey is like the "max" in rule "max: 6"
RulePattern string // RulePattern is like "6" in rule:"max:6"
Field string // The field name of Value.
Value *gvar.Var // Value specifies the value for this rule to validate.
Data *gvar.Var // Data specifies the `data` which is passed to the Validator.
Message string // Message specifies the custom error message or configured i18n message for this rule.
Option RunOption // Option provides extra configuration for validation rule.
RuleKey string // RuleKey is like the "max" in rule "max: 6"
RulePattern string // RulePattern is like "6" in rule:"max:6"
Field string // The field name of Value.
ValueType reflect.Type // ValueType specifies the type of the value, which might be nil.
Value *gvar.Var // Value specifies the value for this rule to validate.
Data *gvar.Var // Data specifies the `data` which is passed to the Validator.
Message string // Message specifies the custom error message or configured i18n message for this rule.
Option RunOption // Option provides extra configuration for validation rule.
}

type RunOption struct {
Expand Down
66 changes: 33 additions & 33 deletions util/gvalid/internal/builtin/builtin_enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ package builtin
import (
"errors"
"fmt"
"reflect"

"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
Expand All @@ -39,40 +37,42 @@ func (r RuleEnums) Message() string {
}

func (r RuleEnums) Run(in RunInput) error {
originTypeAndKind := reflection.OriginTypeAndKind(in.Data.Val())
switch originTypeAndKind.OriginKind {
case reflect.Struct:
for i := 0; i < originTypeAndKind.OriginType.NumField(); i++ {
field := originTypeAndKind.OriginType.Field(i)
if in.Field == field.Name {
var (
typeId = fmt.Sprintf(`%s.%s`, field.Type.PkgPath(), field.Type.Name())
tagEnums = gtag.GetEnumsByType(typeId)
)
if tagEnums == "" {
return gerror.NewCodef(
gcode.CodeInvalidOperation,
`no enums found for type "%s", missing using command "gf gen enums"?`,
typeId,
)
}
var enumsValues = make([]interface{}, 0)
if err := json.Unmarshal([]byte(tagEnums), &enumsValues); err != nil {
return err
}
if !gstr.InArray(gconv.Strings(enumsValues), in.Value.String()) {
return errors.New(gstr.Replace(
in.Message, `{enums}`, tagEnums,
))
}
}
}

default:
if in.ValueType == nil {
return gerror.NewCode(
gcode.CodeInvalidParameter,
`value type cannot be empty to use validation rule "enums"`,
)
}
var (
pkgPath = in.ValueType.PkgPath()
typeName = in.ValueType.Name()
)
if pkgPath == "" {
return gerror.NewCodef(
gcode.CodeInvalidOperation,
`no pkg path found for type "%s"`,
in.ValueType.String(),
)
}
var (
typeId = fmt.Sprintf(`%s.%s`, pkgPath, typeName)
tagEnums = gtag.GetEnumsByType(typeId)
)
if tagEnums == "" {
return gerror.NewCodef(
gcode.CodeInvalidOperation,
`"enums" validation rule can only be used in struct validation currently`,
`no enums found for type "%s", missing using command "gf gen enums"?`,
typeId,
)
}
var enumsValues = make([]interface{}, 0)
if err := json.Unmarshal([]byte(tagEnums), &enumsValues); err != nil {
return err
}
if !gstr.InArray(gconv.Strings(enumsValues), in.Value.String()) {
return errors.New(gstr.Replace(
in.Message, `{enums}`, tagEnums,
))
}
return nil
}